akarshanxyz 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,422 @@
1
+ import express from "express";
2
+ import mongoose from "mongoose";
3
+ import cors from "cors";
4
+
5
+ import React, { useEffect, useState } from "react";
6
+ import {
7
+ configureStore,
8
+ createAsyncThunk,
9
+ createSlice,
10
+ } from "@reduxjs/toolkit";
11
+
12
+ import {
13
+ Provider,
14
+ useDispatch,
15
+ useSelector,
16
+ } from "react-redux";
17
+
18
+ /* ================= BACKEND ================= */
19
+
20
+ const app = express();
21
+
22
+ app.use(cors());
23
+ app.use(express.json());
24
+
25
+ mongoose.connect("mongodb://127.0.0.1:27017/ecommerce");
26
+
27
+ const productSchema = new mongoose.Schema({
28
+ title: String,
29
+ price: Number,
30
+ category: String,
31
+ });
32
+
33
+ const Product = mongoose.model(
34
+ "Product",
35
+ productSchema
36
+ );
37
+
38
+ const seedProducts = async () => {
39
+ const count = await Product.countDocuments();
40
+
41
+ if (count === 0) {
42
+ await Product.insertMany([
43
+ {
44
+ title: "Laptop",
45
+ price: 50000,
46
+ category: "electronics",
47
+ },
48
+ {
49
+ title: "Phone",
50
+ price: 20000,
51
+ category: "electronics",
52
+ },
53
+ {
54
+ title: "TV",
55
+ price: 40000,
56
+ category: "electronics",
57
+ },
58
+ {
59
+ title: "Speaker",
60
+ price: 5000,
61
+ category: "electronics",
62
+ },
63
+ {
64
+ title: "Camera",
65
+ price: 35000,
66
+ category: "electronics",
67
+ },
68
+
69
+ {
70
+ title: "Shirt",
71
+ price: 1500,
72
+ category: "fashion",
73
+ },
74
+ {
75
+ title: "Jeans",
76
+ price: 2500,
77
+ category: "fashion",
78
+ },
79
+ {
80
+ title: "Shoes",
81
+ price: 3000,
82
+ category: "fashion",
83
+ },
84
+ {
85
+ title: "Jacket",
86
+ price: 4500,
87
+ category: "fashion",
88
+ },
89
+ {
90
+ title: "Watch",
91
+ price: 5000,
92
+ category: "fashion",
93
+ },
94
+
95
+ {
96
+ title: "Rice",
97
+ price: 1200,
98
+ category: "grocery",
99
+ },
100
+ {
101
+ title: "Milk",
102
+ price: 60,
103
+ category: "grocery",
104
+ },
105
+ {
106
+ title: "Bread",
107
+ price: 40,
108
+ category: "grocery",
109
+ },
110
+ {
111
+ title: "Eggs",
112
+ price: 120,
113
+ category: "grocery",
114
+ },
115
+ {
116
+ title: "Sugar",
117
+ price: 50,
118
+ category: "grocery",
119
+ },
120
+
121
+ {
122
+ title: "Keyboard",
123
+ price: 1800,
124
+ category: "electronics",
125
+ },
126
+ {
127
+ title: "Mouse",
128
+ price: 700,
129
+ category: "electronics",
130
+ },
131
+ {
132
+ title: "Sneakers",
133
+ price: 3500,
134
+ category: "fashion",
135
+ },
136
+ {
137
+ title: "Oil",
138
+ price: 180,
139
+ category: "grocery",
140
+ },
141
+ {
142
+ title: "T-shirt",
143
+ price: 800,
144
+ category: "fashion",
145
+ },
146
+ ]);
147
+ }
148
+ };
149
+
150
+ seedProducts();
151
+
152
+ app.get("/products", async (req, res) => {
153
+ const page = Number(req.query.page) || 1;
154
+ const limit = Number(req.query.limit) || 5;
155
+
156
+ const totalProducts =
157
+ await Product.countDocuments();
158
+
159
+ const totalPages = Math.ceil(
160
+ totalProducts / limit
161
+ );
162
+
163
+ const products = await Product.find()
164
+ .skip((page - 1) * limit)
165
+ .limit(limit);
166
+
167
+ res.json({
168
+ products,
169
+ currentPage: page,
170
+ totalPages,
171
+ });
172
+ });
173
+
174
+ app.get(
175
+ "/products/category/:category",
176
+ async (req, res) => {
177
+ const products = await Product.find({
178
+ category: req.params.category,
179
+ });
180
+
181
+ res.json({ products });
182
+ }
183
+ );
184
+
185
+ app.listen(5000, () => {
186
+ console.log("Server running on port 5000");
187
+ });
188
+
189
+ /* ================= FRONTEND ================= */
190
+
191
+ type ProductType = {
192
+ _id: string;
193
+ title: string;
194
+ price: number;
195
+ category: string;
196
+ };
197
+
198
+ type ProductState = {
199
+ pages: {
200
+ [key: number]: ProductType[];
201
+ };
202
+
203
+ currentPage: number;
204
+ totalPages: number;
205
+ filteredProducts: ProductType[];
206
+ };
207
+
208
+ const fetchProducts = createAsyncThunk(
209
+ "products/fetchProducts",
210
+ async (page: number) => {
211
+ const response = await fetch(
212
+ `http://localhost:5000/products?page=${page}&limit=5`
213
+ );
214
+
215
+ return response.json();
216
+ }
217
+ );
218
+
219
+ const fetchCategoryProducts =
220
+ createAsyncThunk(
221
+ "products/fetchCategoryProducts",
222
+ async (category: string) => {
223
+ const response = await fetch(
224
+ `http://localhost:5000/products/category/${category}`
225
+ );
226
+
227
+ return response.json();
228
+ }
229
+ );
230
+
231
+ const initialState: ProductState = {
232
+ pages: {},
233
+ currentPage: 1,
234
+ totalPages: 1,
235
+ filteredProducts: [],
236
+ };
237
+
238
+ const productSlice = createSlice({
239
+ name: "products",
240
+
241
+ initialState,
242
+
243
+ reducers: {
244
+ setCurrentPage: (state, action) => {
245
+ state.currentPage = action.payload;
246
+ },
247
+ },
248
+
249
+ extraReducers: (builder) => {
250
+ builder.addCase(
251
+ fetchProducts.fulfilled,
252
+ (state, action) => {
253
+ state.pages[action.payload.currentPage] =
254
+ action.payload.products;
255
+
256
+ state.currentPage =
257
+ action.payload.currentPage;
258
+
259
+ state.totalPages =
260
+ action.payload.totalPages;
261
+ }
262
+ );
263
+
264
+ builder.addCase(
265
+ fetchCategoryProducts.fulfilled,
266
+ (state, action) => {
267
+ state.filteredProducts =
268
+ action.payload.products;
269
+ }
270
+ );
271
+ },
272
+ });
273
+
274
+ const { setCurrentPage } =
275
+ productSlice.actions;
276
+
277
+ const store = configureStore({
278
+ reducer: {
279
+ products: productSlice.reducer,
280
+ },
281
+ });
282
+
283
+ type RootState = ReturnType<
284
+ typeof store.getState
285
+ >;
286
+
287
+ type AppDispatch = typeof store.dispatch;
288
+
289
+ const ProductList = () => {
290
+ const dispatch = useDispatch<AppDispatch>();
291
+
292
+ const {
293
+ pages,
294
+ currentPage,
295
+ totalPages,
296
+ filteredProducts,
297
+ } = useSelector(
298
+ (state: RootState) => state.products
299
+ );
300
+
301
+ const [category, setCategory] =
302
+ useState("");
303
+
304
+ useEffect(() => {
305
+ if (!pages[currentPage]) {
306
+ dispatch(fetchProducts(currentPage));
307
+ }
308
+ }, [currentPage]);
309
+
310
+ const handleNext = () => {
311
+ if (currentPage < totalPages) {
312
+ dispatch(
313
+ setCurrentPage(currentPage + 1)
314
+ );
315
+ }
316
+ };
317
+
318
+ const handlePrevious = () => {
319
+ if (currentPage > 1) {
320
+ dispatch(
321
+ setCurrentPage(currentPage - 1)
322
+ );
323
+ }
324
+ };
325
+
326
+ const handleCategory = (
327
+ e: React.ChangeEvent<HTMLSelectElement>
328
+ ) => {
329
+ const value = e.target.value;
330
+
331
+ setCategory(value);
332
+
333
+ if (value) {
334
+ dispatch(fetchCategoryProducts(value));
335
+ }
336
+ };
337
+
338
+ const products = category
339
+ ? filteredProducts
340
+ : pages[currentPage] || [];
341
+
342
+ return (
343
+ <div style={{ padding: "20px" }}>
344
+ <h1>Product Listing</h1>
345
+
346
+ <select
347
+ value={category}
348
+ onChange={handleCategory}
349
+ >
350
+ <option value="">All</option>
351
+
352
+ <option value="electronics">
353
+ Electronics
354
+ </option>
355
+
356
+ <option value="fashion">
357
+ Fashion
358
+ </option>
359
+
360
+ <option value="grocery">
361
+ Grocery
362
+ </option>
363
+ </select>
364
+
365
+ <div>
366
+ {products.map((product) => (
367
+ <div
368
+ key={product._id}
369
+ style={{
370
+ border: "1px solid gray",
371
+ margin: "10px 0",
372
+ padding: "10px",
373
+ }}
374
+ >
375
+ <h3>{product.title}</h3>
376
+
377
+ <p>₹{product.price}</p>
378
+
379
+ <p>{product.category}</p>
380
+ </div>
381
+ ))}
382
+ </div>
383
+
384
+ {!category && (
385
+ <div>
386
+ <button
387
+ onClick={handlePrevious}
388
+ disabled={currentPage === 1}
389
+ >
390
+ Previous
391
+ </button>
392
+
393
+ <span
394
+ style={{ margin: "0 10px" }}
395
+ >
396
+ Page {currentPage} of{" "}
397
+ {totalPages}
398
+ </span>
399
+
400
+ <button
401
+ onClick={handleNext}
402
+ disabled={
403
+ currentPage === totalPages
404
+ }
405
+ >
406
+ Next
407
+ </button>
408
+ </div>
409
+ )}
410
+ </div>
411
+ );
412
+ };
413
+
414
+ const App = () => {
415
+ return (
416
+ <Provider store={store}>
417
+ <ProductList />
418
+ </Provider>
419
+ );
420
+ };
421
+
422
+ export default App;
@@ -0,0 +1,132 @@
1
+ import React from "react";
2
+ import { useForm } from "react-hook-form";
3
+ import { yupResolver } from "@hookform/resolvers/yup";
4
+ import * as yup from "yup";
5
+
6
+ type FormData = {
7
+ email: string;
8
+ phoneNumber: string;
9
+ paymentMethod: "Card" | "UPI";
10
+ cardNumber?: string;
11
+ cvv?: string;
12
+ upiId?: string;
13
+ terms: boolean;
14
+ };
15
+
16
+ const schema = yup.object({
17
+ email: yup
18
+ .string()
19
+ .email("Invalid email")
20
+ .required("Email is required"),
21
+
22
+ phoneNumber: yup
23
+ .string()
24
+ .matches(/^[0-9]{10}$/, "Phone number must be exactly 10 digits")
25
+ .required("Phone number is required"),
26
+
27
+ paymentMethod: yup
28
+ .string()
29
+ .oneOf(["Card", "UPI"])
30
+ .required("Payment method is required"),
31
+
32
+ cardNumber: yup.string().when("paymentMethod", {
33
+ is: "Card",
34
+ then: (schema) =>
35
+ schema
36
+ .required("Card number is required")
37
+ .matches(/^[0-9]{16}$/, "Card number must be 16 digits"),
38
+ otherwise: (schema) => schema.notRequired(),
39
+ }),
40
+
41
+ cvv: yup.string().when("paymentMethod", {
42
+ is: "Card",
43
+ then: (schema) =>
44
+ schema
45
+ .required("CVV is required")
46
+ .matches(/^[0-9]{3}$/, "CVV must be 3 digits"),
47
+ otherwise: (schema) => schema.notRequired(),
48
+ }),
49
+
50
+ upiId: yup.string().when("paymentMethod", {
51
+ is: "UPI",
52
+ then: (schema) =>
53
+ schema
54
+ .required("UPI ID is required")
55
+ .matches(
56
+ /^[a-zA-Z0-9._-]+@[a-zA-Z]+$/,
57
+ "Invalid UPI ID format"
58
+ ),
59
+ otherwise: (schema) => schema.notRequired(),
60
+ }),
61
+
62
+ terms: yup
63
+ .boolean()
64
+ .oneOf([true], "You must accept terms and conditions"),
65
+ });
66
+
67
+ export default function CheckoutForm() {
68
+ const {
69
+ register,
70
+ handleSubmit,
71
+ watch,
72
+ formState: { errors },
73
+ } = useForm<FormData>({
74
+ resolver: yupResolver(schema),
75
+ });
76
+
77
+ const paymentMethod = watch("paymentMethod");
78
+
79
+ const onSubmit = (data: FormData) => {
80
+ console.log(data);
81
+ };
82
+
83
+ return (
84
+ <form onSubmit={handleSubmit(onSubmit)}>
85
+ <input type="email" placeholder="Email" {...register("email")} />
86
+ <p>{errors.email?.message}</p>
87
+
88
+ <input
89
+ type="text"
90
+ placeholder="Phone Number"
91
+ {...register("phoneNumber")}
92
+ />
93
+ <p>{errors.phoneNumber?.message}</p>
94
+
95
+ <select {...register("paymentMethod")}>
96
+ <option value="">Select Payment Method</option>
97
+ <option value="Card">Card</option>
98
+ <option value="UPI">UPI</option>
99
+ </select>
100
+ <p>{errors.paymentMethod?.message}</p>
101
+
102
+ {paymentMethod === "Card" && (
103
+ <>
104
+ <input
105
+ type="text"
106
+ placeholder="Card Number"
107
+ {...register("cardNumber")}
108
+ />
109
+ <p>{errors.cardNumber?.message}</p>
110
+
111
+ <input type="text" placeholder="CVV" {...register("cvv")} />
112
+ <p>{errors.cvv?.message}</p>
113
+ </>
114
+ )}
115
+
116
+ {paymentMethod === "UPI" && (
117
+ <>
118
+ <input type="text" placeholder="UPI ID" {...register("upiId")} />
119
+ <p>{errors.upiId?.message}</p>
120
+ </>
121
+ )}
122
+
123
+ <label>
124
+ <input type="checkbox" {...register("terms")} />
125
+ Accept Terms & Conditions
126
+ </label>
127
+ <p>{errors.terms?.message}</p>
128
+
129
+ <button type="submit">Pay Now</button>
130
+ </form>
131
+ );
132
+ }
@@ -0,0 +1,199 @@
1
+ import React from "react";
2
+ import { useForm } from "react-hook-form";
3
+ import { z } from "zod";
4
+ import { zodResolver } from "@hookform/resolvers/zod";
5
+
6
+ const sellerSchema = z
7
+ .object({
8
+ firstName: z.string().min(2, "First name must be at least 2 characters"),
9
+ lastName: z.string().optional(),
10
+ email: z.string().email("Invalid email"),
11
+ phoneNumber: z
12
+ .string()
13
+ .regex(/^[0-9]{10}$/, "Phone number must be exactly 10 digits"),
14
+ age: z.number().min(18, "Age must be 18 or above"),
15
+
16
+ password: z.string().min(6, "Password must be at least 6 characters"),
17
+ confirmPassword: z.string(),
18
+
19
+ accountType: z.enum(["Individual", "Business"]),
20
+
21
+ shopName: z.string().min(1, "Shop name is required"),
22
+ gstNumber: z
23
+ .string()
24
+ .regex(/^[A-Z0-9]{5,15}$/, "Invalid GST number"),
25
+ businessAddress: z
26
+ .string()
27
+ .min(1, "Business address is required"),
28
+
29
+ address: z.object({
30
+ street: z.string().min(1, "Street is required"),
31
+ city: z.string().min(1, "City is required"),
32
+ state: z.string().min(1, "State is required"),
33
+ pincode: z
34
+ .string()
35
+ .regex(/^[0-9]{6}$/, "Pincode must be exactly 6 digits"),
36
+ }),
37
+
38
+ categories: z
39
+ .array(z.string())
40
+ .min(1, "Select at least one category"),
41
+
42
+ profileImage: z.any().optional(),
43
+
44
+ idProof: z.any().refine((file) => file?.length > 0, {
45
+ message: "ID Proof is required",
46
+ }),
47
+
48
+ bankDetails: z.object({
49
+ accountNumber: z.string().min(1, "Account number is required"),
50
+ ifscCode: z
51
+ .string()
52
+ .regex(/^[A-Z]{4}0[A-Z0-9]{6}$/, "Invalid IFSC code"),
53
+ accountHolderName: z
54
+ .string()
55
+ .min(1, "Account holder name is required"),
56
+ }),
57
+
58
+ terms: z.literal(true, {
59
+ errorMap: () => ({
60
+ message: "You must accept terms and conditions",
61
+ }),
62
+ }),
63
+ })
64
+ .refine((data) => data.password === data.confirmPassword, {
65
+ path: ["confirmPassword"],
66
+ message: "Passwords do not match",
67
+ });
68
+
69
+ type SellerFormData = z.infer<typeof sellerSchema>;
70
+
71
+ export default function SellerRegistrationForm() {
72
+ const {
73
+ register,
74
+ handleSubmit,
75
+ formState: { errors },
76
+ } = useForm<SellerFormData>({
77
+ resolver: zodResolver(sellerSchema),
78
+ });
79
+
80
+ const onSubmit = (data: SellerFormData) => {
81
+ console.log(data);
82
+ };
83
+
84
+ return (
85
+ <form onSubmit={handleSubmit(onSubmit)}>
86
+ <input placeholder="First Name" {...register("firstName")} />
87
+ <p>{errors.firstName?.message}</p>
88
+
89
+ <input placeholder="Last Name" {...register("lastName")} />
90
+ <p>{errors.lastName?.message}</p>
91
+
92
+ <input placeholder="Email" {...register("email")} />
93
+ <p>{errors.email?.message}</p>
94
+
95
+ <input placeholder="Phone Number" {...register("phoneNumber")} />
96
+ <p>{errors.phoneNumber?.message}</p>
97
+
98
+ <input
99
+ type="number"
100
+ placeholder="Age"
101
+ {...register("age", { valueAsNumber: true })}
102
+ />
103
+ <p>{errors.age?.message}</p>
104
+
105
+ <input
106
+ type="password"
107
+ placeholder="Password"
108
+ {...register("password")}
109
+ />
110
+ <p>{errors.password?.message}</p>
111
+
112
+ <input
113
+ type="password"
114
+ placeholder="Confirm Password"
115
+ {...register("confirmPassword")}
116
+ />
117
+ <p>{errors.confirmPassword?.message}</p>
118
+
119
+ <select {...register("accountType")}>
120
+ <option value="">Select Account Type</option>
121
+ <option value="Individual">Individual</option>
122
+ <option value="Business">Business</option>
123
+ </select>
124
+ <p>{errors.accountType?.message}</p>
125
+
126
+ <input placeholder="Shop Name" {...register("shopName")} />
127
+ <p>{errors.shopName?.message}</p>
128
+
129
+ <input placeholder="GST Number" {...register("gstNumber")} />
130
+ <p>{errors.gstNumber?.message}</p>
131
+
132
+ <input
133
+ placeholder="Business Address"
134
+ {...register("businessAddress")}
135
+ />
136
+ <p>{errors.businessAddress?.message}</p>
137
+
138
+ <input placeholder="Street" {...register("address.street")} />
139
+ <p>{errors.address?.street?.message}</p>
140
+
141
+ <input placeholder="City" {...register("address.city")} />
142
+ <p>{errors.address?.city?.message}</p>
143
+
144
+ <input placeholder="State" {...register("address.state")} />
145
+ <p>{errors.address?.state?.message}</p>
146
+
147
+ <input placeholder="Pincode" {...register("address.pincode")} />
148
+ <p>{errors.address?.pincode?.message}</p>
149
+
150
+ <label>
151
+ <input type="checkbox" value="Electronics" {...register("categories")} />
152
+ Electronics
153
+ </label>
154
+
155
+ <label>
156
+ <input type="checkbox" value="Fashion" {...register("categories")} />
157
+ Fashion
158
+ </label>
159
+
160
+ <label>
161
+ <input type="checkbox" value="Books" {...register("categories")} />
162
+ Books
163
+ </label>
164
+
165
+ <p>{errors.categories?.message}</p>
166
+
167
+ <input type="file" {...register("profileImage")} />
168
+
169
+ <input type="file" {...register("idProof")} />
170
+ <p>{errors.idProof?.message as string}</p>
171
+
172
+ <input
173
+ placeholder="Account Number"
174
+ {...register("bankDetails.accountNumber")}
175
+ />
176
+ <p>{errors.bankDetails?.accountNumber?.message}</p>
177
+
178
+ <input
179
+ placeholder="IFSC Code"
180
+ {...register("bankDetails.ifscCode")}
181
+ />
182
+ <p>{errors.bankDetails?.ifscCode?.message}</p>
183
+
184
+ <input
185
+ placeholder="Account Holder Name"
186
+ {...register("bankDetails.accountHolderName")}
187
+ />
188
+ <p>{errors.bankDetails?.accountHolderName?.message}</p>
189
+
190
+ <label>
191
+ <input type="checkbox" {...register("terms")} />
192
+ Accept Terms & Conditions
193
+ </label>
194
+ <p>{errors.terms?.message}</p>
195
+
196
+ <button type="submit">Register Seller</button>
197
+ </form>
198
+ );
199
+ }
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+
3
+ const registerSchema = z
4
+ .object({
5
+ fullName: z.string().min(2, "Full name must be at least 2 characters"),
6
+ email: z.string().email("Invalid email"),
7
+ password: z.string().min(6, "Password must be at least 6 characters"),
8
+ confirmPassword: z.string(),
9
+ age: z.number().min(19, "Age must be above 18"),
10
+ })
11
+ .refine((data) => data.password === data.confirmPassword, {
12
+ message: "Passwords do not match",
13
+ path: ["confirmPassword"],
14
+ });
15
+
16
+ type RegisterFormData = z.infer<typeof registerSchema>;
17
+
18
+ const formData: RegisterFormData = {
19
+ fullName: "Bharat",
20
+ email: "bharat@example.com",
21
+ password: "123456",
22
+ confirmPassword: "123456",
23
+ age: 20,
24
+ };
25
+
26
+ const result = registerSchema.safeParse(formData);
27
+
28
+ if (!result.success) {
29
+ console.log(result.error.flatten().fieldErrors);
30
+ } else {
31
+ console.log("Valid Data", result.data);
32
+ }
@@ -0,0 +1,49 @@
1
+ // productSlice.js
2
+
3
+ import { createSlice } from "@reduxjs/toolkit";
4
+
5
+ const initialState = {
6
+ products: [
7
+ {
8
+ id: 1,
9
+ title: "Handmade Vase",
10
+ price: 800,
11
+ stock: 5,
12
+ },
13
+ {
14
+ id: 2,
15
+ title: "Wooden Craft",
16
+ price: 1200,
17
+ stock: 3,
18
+ },
19
+ ],
20
+ };
21
+
22
+ const productSlice = createSlice({
23
+ name: "products",
24
+ initialState,
25
+ reducers: {
26
+ decreaseStock: (state, action) => {
27
+ const product = state.products.find(
28
+ (item) => item.id === action.payload
29
+ );
30
+
31
+ if (product && product.stock > 0) {
32
+ product.stock -= 1;
33
+ }
34
+ },
35
+
36
+ increaseStock: (state, action) => {
37
+ const product = state.products.find(
38
+ (item) => item.id === action.payload
39
+ );
40
+
41
+ if (product) {
42
+ product.stock += 1;
43
+ }
44
+ },
45
+ },
46
+ });
47
+
48
+ export const { decreaseStock, increaseStock } = productSlice.actions;
49
+ export default productSlice.reducer;
@@ -0,0 +1,141 @@
1
+ import React, {
2
+ Suspense,
3
+ lazy,
4
+ useEffect,
5
+ useState,
6
+ } from "react";
7
+
8
+ type Product = {
9
+ id: number;
10
+ title: string;
11
+ price: number;
12
+ thumbnail: string;
13
+ };
14
+
15
+ type ProductCardProps = {
16
+ title: string;
17
+ price: number;
18
+ thumbnail: string;
19
+ };
20
+
21
+ const ProductCard = ({
22
+ title,
23
+ price,
24
+ thumbnail,
25
+ }: ProductCardProps) => {
26
+ return (
27
+ <div
28
+ style={{
29
+ border: "1px solid gray",
30
+ padding: "10px",
31
+ marginBottom: "10px",
32
+ width: "250px",
33
+ }}
34
+ >
35
+ <img
36
+ src={thumbnail}
37
+ alt={title}
38
+ width="200"
39
+ height="150"
40
+ />
41
+
42
+ <h3>{title}</h3>
43
+
44
+ <p>${price}</p>
45
+ </div>
46
+ );
47
+ };
48
+
49
+ const ProductListComponent = () => {
50
+ const [products, setProducts] = useState<Product[]>([]);
51
+ const [skip, setSkip] = useState(0);
52
+
53
+ useEffect(() => {
54
+ const controller = new AbortController();
55
+
56
+ const fetchProducts = async () => {
57
+ try {
58
+ const response = await fetch(
59
+ `https://dummyjson.com/products?limit=5&skip=${skip}`,
60
+ {
61
+ signal: controller.signal,
62
+ }
63
+ );
64
+
65
+ const data = await response.json();
66
+
67
+ setProducts((prev) => [
68
+ ...prev,
69
+ ...data.products,
70
+ ]);
71
+ } catch (error: any) {
72
+ if (error.name === "AbortError") {
73
+ console.log("Request Cancelled");
74
+ }
75
+ }
76
+ };
77
+
78
+ fetchProducts();
79
+
80
+ return () => {
81
+ controller.abort();
82
+ };
83
+ }, [skip]);
84
+
85
+ return (
86
+ <div>
87
+ <h2>Products</h2>
88
+
89
+ {products.map((product) => (
90
+ <ProductCard
91
+ key={product.id}
92
+ title={product.title}
93
+ price={product.price}
94
+ thumbnail={product.thumbnail}
95
+ />
96
+ ))}
97
+
98
+ <button onClick={() => setSkip((prev) => prev + 5)}>
99
+ Load More
100
+ </button>
101
+ </div>
102
+ );
103
+ };
104
+
105
+ const ProductList = lazy(
106
+ () =>
107
+ new Promise<{ default: React.ComponentType }>(
108
+ (resolve) => {
109
+ setTimeout(() => {
110
+ resolve({
111
+ default: ProductListComponent,
112
+ });
113
+ }, 1000);
114
+ }
115
+ )
116
+ );
117
+
118
+ const App = () => {
119
+ const [showProducts, setShowProducts] =
120
+ useState(false);
121
+
122
+ return (
123
+ <div>
124
+ <h1>Performance Optimization Demo</h1>
125
+
126
+ <button onClick={() => setShowProducts(true)}>
127
+ Show Products
128
+ </button>
129
+
130
+ {showProducts && (
131
+ <Suspense
132
+ fallback={<h2>Loading Products...</h2>}
133
+ >
134
+ <ProductList />
135
+ </Suspense>
136
+ )}
137
+ </div>
138
+ );
139
+ };
140
+
141
+ export default App;
package/package.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "akarshanxyz",
3
- "version": "1.0.0"
3
+ "version": "1.0.1"
4
4
  }