Skip to content

Latest commit

 

History

History
1019 lines (673 loc) · 47.5 KB

File metadata and controls

1019 lines (673 loc) · 47.5 KB

E-commerce MERN Project

Tech Stack

Frontend

  • React
  • React-router
  • React-query or tanstack (for data fetching)
  • TailwindCSS
  • TypeScript
  • react-toastify
  • shadcn
  • lucide-react
  • react-hook-form
  • zod

Backend

  • Node.js
  • Express
  • MongoDB
  • Mongoose
  • TypeScript
  • cors
  • bycrypt
  • dotenv
  • jsonwebtoken
  • express-validator
  • nodemon
  • multer
  • cloudinary

Step-by-step Dev Log with client folder first

1. Create a new project called e-commerce-mern-app
2. create main directories "client", "server" and "admin"

Step-by-step Dev Log with client folder first

3. Start with the client directory
4. Install TailwindCSS vite using npm
5. Install React-router using npm
6. Create .env file
7. Create folders "components", "pages", "layouts", "routes"
8. Create "Home.tsx" page
9. Create "MainLayout.tsx" layout in "MainLayout" folder inside "layouts" directory
10. Create "Header.tsx", "Footer.tsx" component in "common" folder inside "components" directory
11. Create "index.ts" file inside "common" folder inside "components" directory
12. Create "AppRouter.tsx" file inside "routes" folder
13. Create routes using createBrowserRouter in AppRouter.tsx file
14. Import "AppRouter" in "main.tsx" file
15. Setup "path alias" in "vite.config.ts" and "tsconfig.app.json" file to resolve the path
16. Add color palette in "index.css" file
17. Add height "min-h-dvh" to the whole dev container in "MainLayout.tsx" file
18. Add "grow" to "main" in "MainLayout.tsx" file to make the main container grow with the content
19. Add "fixed top-0 left-0 w-full z-50 h-16" to "header" tag in "Header.tsx" file and "h-full" to "container" to center the items in header after adding the height
20. After making the header fixed, add 'pt-16' to "main" tag in "MainLayout.tsx" file to make the main container start from the top of the header
21. Install daisyUI library
22. Add "Drawer" from "daisyUI" to handle responsive navigation
23. Enhance "Header" by adding links to array and loop through the links, and use 'memo' to optimize performance
24. Add "Footer"
25. Add 'ThemeController' in to handle theme change in "MainLayout.tsx" file
26. Add my custom colors in "index.css" file
27. add 'data-theme' attribute to the html tag in "index.html" file
28. Update "Header.tsx" file to use 'data-theme' attribute to change the theme
29. Update "Hero.tsx" file to use 'data-theme' attribute to change the theme
30. Add fixed padding to 'Home.tsx' page to make all sections on the same align
31. Add Features Section
32. Add Categories Section
33. Add "LimitedTimeOffers.tsx" section
34. Add "CountdownTimer.tsx" in separate component
35. I discovered that the colors in index.css such natural, base and primary are built-in colors of daisyUI, so I changed the colors to my custom colors. so when i use "desert-taupe" directly not working
36. I skip colors for now and move on to finish home page so i started in form in contact us section
37. Now after finished home page main design but i will back again to edit colors I will move to backend

Move to backend

38. Install main dependencies
39. First type "npm init" to create a new package.json file
40. Install "express", "mongoose" "jsonwebtoken", "nodemon", 'cors', 'bycrypt and "dotenv" using npm
41. Create "index.ts" file in "server" directory
42. Create "models", "controllers", "routes" and "middlewares" directory in "src" directory
43. Install "typescript" and and init "tsc --init" to create tsconfig.json file
44. Add 'start' script in package.json file with "nodemon index.ts"
45. Add initial "express" code in "index.ts" file to test the server
46. Run "npm start" to start the server
47. Add "port", 'mongoURI' in ".env" file
48. Create "db.ts" mongodb connection in "lib" directory
49. Run "npm start" to start the server

Back to Admin

50. Get the code of customer management dashboard from my previous project "customer-management-react-node"
51. Install dependencies
52. Update "Home" and replace 'customer' with 'products' to display total count of 'users' and 'products'
53. Edit "Customers Page" to "Users Page"
54. Pause protected route for now

Back to backend

55. Get the code of "user" from my previous project "customer-management-react-node"
56. Add 'AddUser', 'deleteUser' and 'editUser' to 'users.controller'
57. Add 'add-user', 'edit-user' and 'delete-user' routes to 'users.route'
58. Add 'country', 'phone', 'gender' to 'user.model
59. Ask about handling 'full name' in 'user.model' if best i get it from user as 'firstName' and 'lastName' or 'fullName'?
60. The answer is 'firstName' and 'lastName' because i'ts easier to combine two fields than to to split
61. Then combine 'firstName' and 'lastName' in 'fullName' field using 'virtual' in 'user.model'
62. If i want 'fullName' appears when send data to frontend then i need to add 'toJSON' and 'toObject' in 'user.model'
63. Add 'enum' in 'gender' field for more strict validation in 'user.model'
64. Understand what is the 'virtuals' and 'enum' in 'user.model'
65. I understand that 'virtuals' is used to add virtual fields to the model and not added to database it's only and 'enum' is built-in validator in mongoose to define restricted values for a field

Back to server

66. Add cors to server
67. Add 'corsOptions' by add origin of the 'admin' in origin are allowed to access

Back to admin

68. Display all users in 'Users' page
69. Work on 'Add User' page
70. for now i will work only on 'AddUserForm' with its 'inputFiled' validations
71. Escape for now reset password after user login
72. focusing only for adding users
73. How the admin will add users without adding password? i will generate a random password and add hidden field in the form so the admin can't see the password

Back to server

74. Add hashed password in 'user.controller' when sending the user data to the database
75. What is the difference between 'create' and 'new'?
i found that new User() + save: when i want to update any data before saving it to the database
create User(): when i want to create a new user and save it to the database without updating any data
76. I use 'transform' in 'toJSON' to remove password from the response
77. Finished the 'Add User' page
78. Move to 'Products' page
79. Add main titles in products table 'Name', 'Category', 'Price', 'Image', 'Description', 'inStock'

Back to server

80. Add product model, controller and route
81. Add "AddProduct", "getProducts in "products.controller"

Back to admin

82. Add "AddProductPage" in "admin"
83. When i add 'add-product' route in "sidebar" it makes products and addProduct active so i use end in navLink to solve this problem
84. Handle "AddProductForm" in "AddProduct" page
85. Add logic to handle "AddProductForm" in "AddProduct" page
86. I Faced a react-hook-form problem in number and boolean types so i use coerce.number() and coerce.boolean() in zod schema, but i faced another problem in resolver in 'addProductForm' so i will search for it
87. First i faced a problem in resolver in 'addProductForm' so i searched and asked ai
- first i removed product interface from productSchema.ts
- second i used z.input and z.output instead of z.infer because i want to get the data from the form in way and then zod will validate it and resolve it to the correct type
- third i go to 'AddProductForm' and used "parse method" to parse the data from the form to the correct type
88. handle crud logics to 'AddProductForm' in 'AddProduct' page
89. I faced a problem in "ProductsList" page with type of id is i add it in "productSchema.ts" file or what???
i solved it by adding "id" in "productSchema.ts" file
90. handle 'delete product' in 'ProductsList' page

Back to server

91. Add "deleteProduct" logic in "products.controller" by using "findByIdAndDelete" method
92. Add "deleteUser" logic in "users.controller" by using "findByIdAndDelete" method

Back to admin

93. Add "deleteUser" logic in "Users page"
94. Add "viewUser" logic in "Users page" by create "userDetails" Page

Back to server

95. Add "timestamps" in 'user.model' to add createdAt and updatedAt fields
96. I faced a problem in displaying the date in users the timestamps not work
i solved it by just restarting the laptop

Back to admin

97. To convert date retrieved from database to date format in frontend use "toLocaleDateString()" method
98. Create "ProductDetails" page
99. Handle AddProductForm in "AddProduct" page the submit button not working
I found the problem in "productSchema.ts" which i was write _id: z.string() and this is means required field so i add "optional()" to make it optional
100. Then active view product button
101. AFter i add 'optional()' to _id in productSchema.ts file i faced a problem in "ProductsList" page because i use "_id" in all actions
i solved it by adding interface IProduct in "ProductsList" page instead of IProductForm
102. Handle Edit event in "ProductsList" page

Back to server

103. Add "update" route in "products.route.ts
104. Add "updateProduct" function in "products.controller.ts"

Back to admin

105. Add "editProduct" function in "ProductsList" and navigate to "AddProductPage"
106. Handle "AddProductPage" to contain both cases "Add" and "Update"
107. First enhance "ProductsList" page to separate files
108. Move IProduct interface into "product.types.ts" into "types" directory
109. Add "IProductsResponse" interface into "product.types.ts" into "types" directory and use it in useQuery
110. I create "common" folder inside "components" directory and move "Header" and "Sidebar" components into it and create "index.ts" file inside "common" directory
111. I create "ui" folder inside "components" directory and create "DeleteButton" inside it and create "index.ts" file inside "ui" directory
112. I create "DeleteButton" for delete product and user with custom confirmation message
113. Download "ShadCn" to use "AlertDialog"
114. Add "compilerOptions" in "tsconfig.json" file
115. Now finished the "DeleteButton" for delete product and user with custom confirmation message
116. Add "SummaryCard" component for "Dashboard"
117. Add Responsive for dashboard
- First install "sheet" from "shadcn"
- add menu icon in "header"
- swap "search" with "profile" in "header"
- add "mobileSidebar" in "header"
- make pages responsive
- starts with "Home" page

Back to server

118. Add Image in product using "Cloudinary"
1- login to cloudinary
2- install "cloudinary" using npm
3- add 'cloudinary cloud_name', 'cloudinary api_key', 'cloudinary api_secret' in '.env' file
4- add "cloudinary.config" in 'index.ts' file with "secure: true" to convert the url to https, and add 'cloud_name', 'api_key', 'api_secret' in "cloudinary.config"
5- I found we can use "CLOUDINARY_URL" in .env instead of "cloudinary cloud_name", "cloudinary api_key", "cloudinary api_secret"
6- Then just write "cloudinary.config" in "index.ts" without any config and it will work
7- test if cloudinary is working by console.log(cloudinary.config()) if print "{}" then it's not working it must print "cloud_name", "api_key", "api_secret"
8- So for now because "cloudinary url" is not working I will use separate api key, api secret and cloud name in ".env" file
9- and now it working successfully
119. install "multer" for uploading files
1- install "multer" using npm
2- add storage in "index.ts" file and use "memoryStorage()" to store the files in memory in RAM and i didn't use "diskStorage()" because i want to use cloudinary for uploading files and i don't want to save the files in the disk
3- create "cloudinary.ts" file in "lib" directory and remove  "cloudinary.config" from index.ts in it
4- create 'multerMiddleware.ts' file in "middlewares" directory to recieve the file from the form and save it in the database and we define type of the file in the middleware "single('image')"
5- add "multerMiddleware" in "products.route.ts" file to use the middleware
6- edit addProduct function in "products.controller.ts" file to use the middleware and add the image to the product
120. Just questions what lib and utils folders means?
- فولدر ال lib ده الل بنحط فيه الخدمات الأساسية الل بنربط بيها خدمات خارجيه أو أنظمه جوا المشروع زى db connection, stripe setup, cloudinary setup, etc.
- فولدر ال utlis ده لل functions الصغيره الل بستخدمها أكتر من مرة
121. just flow of add image in product
1- Express م بيعرف يستقبل ملفات لو بعتله صوره هتوصله بصيغه multipart/form-data و express م فاهمها ف بنستخدم multer library ويفكه
2- استخدمنا memory storage نحظ الصوره على الرام على شكل buffer وبعدين نرفعها على cloudinary
122. now i can upload image in product

Back to Admin

123. in "AddProductForm" in "AddProduct" page change input type="file"
124. Change "image" type in "productSchema.ts" file to "file()" and add strict validation for image size and image type
125. i faced an error in "productSchema.ts" when i define type of "image" as file() in zod schema this error "Invalid input: expected file, received FileList"
I solved it by adding "any()" in zod schema temporarily
126. Add files.length === 1 validation in "productSchema.ts" file to make image required
127. Use formData to send data to the server instead of json and remove "Content-Type" header
128. Handle display image in "ProductsList" page

Back to server

129. Display product image in productDetails page

Back to Admin

130. Handle Edit product because i forget
- I change "AddProductForm" to "ProductForm"
- change text depend on status add or update
- handle get product using useQuery in "ProductForm"
- use reset to display the data in "ProductForm" which i used react-hook-form will handle the form data without needing to add value
- there is a problem in onSuccess of useQuery in "ProductForm" because in new versions of react-query it will removed because what i found that "useQuery is meant to deal with data, not with the fetch process" so it was causes alot of problems because if the data updated or get from cache it will not work
- so i used "useEffect" to handle the data after i fetch it

Back to server

131. fix "editProduct" controller in "products.controller.ts" file by using "new:true" to display the old data before editing and "validators:true" to data validation in schema in "findByIdAndUpdate" method
132. Until now every thing is working fine but i faced a problem with "preview and update" image
- before i told you how i solved this problem i will ask you a question
- How image is stored in the database using cloudinary?
- when i send data from frontend as formData to the server until i can send the image in the formData as file because json can't handle file type
- so the image sended as file this file contain length and files "image"
- so i send the image which is the index 0 because index 1 is the file and length is the length of the file
- the server get the image as string contain file name, file type file size and file buffer
- then i used cloudinary which take the file buffer and upload it to cloudinary and it handle it and convert it to url
- and i send the url to the database

- now after i send the image to the database i can display it in the frontend
- but how i can update it in the database?
    1- first you will not found the image in req.body because it's a file and it's not in the body of the request
    2- so if you want the old image of the product you will get the product data of the id and then you will find the image in the data of the product but how you can update it?
133. How can i update the image in the database in "products.controller.ts" file?
- first i will get old product image from product data
- then get the new image from req.file if exist
- then use the same function i used in addProduct to upload the image to cloudinary and get the url
- then make old image  =  new image
- then use findByIdAndUpdate to update the product

Back to admin

134. I want to preview image in "update" mode
- first i add state "previewImage" in "ProductForm"
- then i use setPreviewImage in useEffect to set the previewImage to the image of the product
- but i faced an error when using setPreviewImage direct after reset because i'm trying to change state twice at the same time can cause rerenders and i will get an error
- so i use setTimeout to wait for the state to be updated
- finally i preview the image using URL.createObjectURL
135. Enhance "Users Page"
- create "PageTitle" component in "common" folder to handle the title of the page
- create "UsersList" component and move table from "users.tsx" to "UsersList" component
136. Handle Edit User

Back to server

137. Edit user _id to id without _ to easy to use in the front-end
138. Add quantity stock to product

Back to Admin

139. Handle Responsive problems
140. Handle long description in "product details"
- adding specific width to td and line-clamp-2 to div
- you will find a problem if you add line-clamp-2 to "td" so the solution is to add "line-clamp-2" to "div" container of description in "td"

Back to server

141. Register
- I asked question what is teh best name for register is "register or signup" and "login or signin"?
- I found that "register and login" is the best names for routes
- and "signup and signin" for ux maybe cause confusion for user so the best is "login and signup" or "register and signin"
- update "user.model" change name of role user to "viewer" this is the best approach for now
- so I have now ["admin", "viewer"] in "user.model"
- admin: can read, create, update, delete
- viewer: read only
-
- the second question is how the admin can register at the first time as admin because we make default role is "viewer"?
    - after i searched i found two solutions:
        - first add fixed mail and password for admin and make role admin
        - second add userCount which is the number of users and if userCount === 0 this means this is the first one who register then make role admin after that new users follow the normal logic
        - I choose the second one because it's more secure and I think it's the best approach

        - I use User model .countDocuments() function from mongoose to get the userCount because each user is a new document in the database
        -
142. Login

Back to Admin

143. Add "Signup" page
144. Add "login" page
- I faced a problem when using AuthFormInput component in "login" page because I was using ISingupFormData type for register and name and all fields so i faced errors with type in login because i have to use TLoginFormData type and these will make me create repeated components

- I used generic instead of specific type:
    - first what is generic type?
        - generic type is a type that can be used in many different places means أنا م عارف نوع الداتا دلوقت بس الل هيستخدمنى هيقولى النوع زى متغير x in math
    - T is the type of data like x in math  variable name
    - FieldValues it's type in react-hook-form  means أى object يمثل form مفاتيحه strings وقيمته اى حاجه
    FieldValues: {
        [key: string]: any;
    }
    يعنى المفتاح نوعه string لكن النوع أى حاجه
    so
    type SignupForm = {
        firstName: string;
        lastName: string;
        email: string;
        password: string;
    }

    type LoginForm = {
        email: string;
        password: string;
    }

    all this FieldValues
    - <T extends FieldValues> معناه إن T لازم يكون object like form بحيث يمنع إنه يكون نوع واحد دايركت بمعنى T = string على طول وده غلط لأن شكل الداتا الل هيتجى من الفورم على شكل اوبجكت وممكن يكون فيه انواع مختلفه {name: string, age: number}
    يعنى ال generic but work only with form not any data

    - name: Path<T> معناه إن ال name لازم يبقى واحد من دول
145. Enhance components names and structure
146. Handle errors in "ProductForm" with type number and understand what FieldErrors[Path]; means

Back to server

147. Handle protected route and not found route
- first one will register will be admin
- anyone who register after admin will be viewer
- admin can add user and make them viewer
- viewer can only view the products
- admin can make all functions
148. Adding not found route using "{0,}" in server/index.ts file because old way "*" in v3 but in v4 by using this way

Back to Admin

149. Make dashboard protected only who logged in can see it
- i add protected route in "AppRouter.tsx" file
- i create 'isAuthenticated' function in "utils" folder which check if the token is in local storage
1510. Add header in 'Home' page to add auth with every request
- which login != permission which protected route proves someone is logged in not they are allowed to see the page
- so i add " headers: { Authorization: `Bearer ${token}` }," in fetch request in "Home.tsx" file
1511. ِHandle all routes with "admin" role
- first i create "getRoleFromToken" function in "utils" folder to get the role from the token using "atob"
- then i create "RoleProtectedRoute" component in "components" folder to check if the role is admin or not
- add "RoleProtectedRoute" in "AppRouter.tsx" file to all admin roles

Back to server

1512. add count routes for both products and users to return the count of products and users using "countDocuments"

Back to admin

1513. Add "productsCount" and "usersCount" in "Home" page

Back to server

1514. add "me" route in "users.route" to get accessible user data for both viewer and admin include only "fullName", "email", "role" using "select" after findById

Back to admin

1515. Display user data "email", "role" in Header profile menu
1516. i handle edit, delete buttons as not allowed for viewer to use them and sidebar to prevent "viewer" to see "users, add user" pages
1517. create "Development_diary.md" file to keep track of the development diary and make Readme for main points
1518. Confirm message before logout "logout" on sidebar using "AlertDialog"
1519. Enhance "AppRouter.tsx" to prevent repetition of code
- create "routePaths.ts" file in "routes" folder to store all routes
- import "routePaths" in "AppRouter.tsx" file and use it in "path" attribute
- create "adminRoutes.ts" file in "routes" folder to store all admin routes
- create "authRoutes.ts" file in "routes" folder to store all auth routes
- import "routePaths" and "adminRoutes" in "AppRouter.tsx" file and use it in "path" attribute
1520. Add "ErrorElement" in "AppRouter.tsx" file to display error message when write wrong path or route
- create "ErrorBoundary" component in "components" folder
    - create "error" variable = useRouteError()
    - create "errorMessage" variable empty and add it's type string
    - then check if the error is "RouteErrorResponse" using "isRouteErrorResponse" which means the error from the server such "401 Unauthorized", "404 Not Found", "403 forbidden" and etc
    - then else if the error not from the server use "instanceof Error" when the error from the code itself or unexpected error such "nul", "undefined", "NaN" and etc such delete "data" from "useQuery" in "ProductsList" page for example
- add "ErrorBoundary" in "AppRouter.tsx" file in "errorElement" attribute
1521. What is "ErrorBoundary" component?
- it's a component that catches errors in the application and displays a fallback UI when an expected error occurs
- we use it to wrap the application or specific components that we want to handle errors for
- if you wrap specific components with ErrorBoundary, it will display  the fallback UI when an error occurs in that component and display other components as it without effecting the rest of the application
-
- ErrorBoundary is a class component use [ComponentDidCatch, getDerivedStateFromError] functions lifecycle methods
-
1522. ايه هى ال lifecycle ؟

ال lifecycle هى مراحل حياة ال component

  • Render, Update, Unmount
  • أي حاجه بتبدأ ب Component عبارة عن class function ومكانها الطبيعى في class لأنها بداية نشأت ال React
  • لأن ال class has instance, this, internal state
  • الل بيحصل إن react بيكريت instance from the class وينادى ال methods in specific times
1522. ليه ال functional component ملهاش lifecycle ؟
  • لأنها زى ما بنقول هى مجرد function بننادى عليها وبتخلص
  • وملهاش instance and this بالتالى مافيش مكان لل lifecycle methods
1522. طب ايه الحل ؟ عملوا ال hooks
  • جابوا ال state and side effects like useEffect instead of [ComponentDidMount, ComponentDidUpdate, ComponentWillUnmount]
  • لكن م كل ال lifecycle methods اتحولت ل hooks
  • لأن ال componentDidCatch and getDerivedStateFromError بتشتغل قبل ال render في مرحلة React الداخلية م جوه ال function

=> hooks بتتنادى أثناء ال Render => وال Render لازم يبقى Pure => فلما Error يحصل ال Render بيتكسر => بالتالى ال hook م هيشتغل عشان كده بنحتاج حاجه تشتغل برا ال Render وم عندناش hook لكده

1522. عندنا حلين إما
- استخدام ال ErrorBoundary with class component
- او استخدام react-error-boundary library

and they suggest to use react-error-boundary library
1523. So we will use "react-error-boundary" library and wrap the whole application with "ErrorBoundary" component
- install "react-error-boundary" using npm
- add "ErrorBoundary" in "AppRouter.tsx" file
- this called global error boundary
- but you should now it's not catch async errors you have to use "useErrorBoundary" hook inside async function by using "showError" function
- and in reactQuery use error and isError and throw error
1524. I faced an error in "Home" page
- I faced this error "Unexpected Application Error!

Objects are not valid as a React child (found: object with keys {msg, data, success}). If you meant to render a collection of children, use an array instead."

  • I found the error comes from "SummaryCard" component in "Home" page
  • first when i console count of products and users i found that he also console users data and senstive data like password I found that i wrote users and products in useQuery in "Home" page so i changed it to "users-count" and "products-count" and it's working fine

move to client

1525. design login and sighup page
- first design login page
    - create "AuthInput" component in "components" folder for input field
    - use react-hook-form and zod validation
    - create "loginSchema" in "schemas" folder for zod validation
    - use "react-query" for fetching data
    - add "useMutation" to send data to the server in login
    - remember we use "useMutation" to send data to the server and "useQuery" to fetch data from the server
    - add "toast" for displaying success and error message
    - solve login problem which i was forget to add body in fetch request
- move to "Signup" page
    - handle "AuthInput" component to handle both login and signup
    - add generic type to "AuthInput" component
        - i used "FieldValues" from "react-hook-form"
        - i used "Path" from "react-hook-form" for name type
        - i used "UseFormRegister" from "react-hook-form" for register type
        - and "FieldError" from "react-hook-form" for error type
        - in "signup" in backend i get 'firstName' and 'lastName' but in 'signup' page i get 'fullName' so i change 'fullName' to 'firstName' and 'lastName'

- finally add 'user' icon in header if user is logged in
1526. display 'products' in 'categories' section in home page
- display 'products' in home page and every product will have a link to the product details page
- keep simple now and only make this home page and header links will scroll to pages section such as 'categories' and 'contact us' without createing new pages
- use 'id' in 'navItems' to make links to scroll to sections and note that 'Link' not work as "a" tag so i will use 'scrollIntoView()' method
- as defualt display 3 rows of products as 'all' category
- use asiox instance in "Categories.tsx" file and back to "products.rotue.ts" and remove "authMiddleware" from "getProducts" route which can any one see products even not logged in
- create "productCard" component to reusable in 'categories' section
- create "productDetails" page and add "id" in url
- add "productDetails" page in "AppRouter.tsx" file
- create 'product.types.ts' file in "types" folder
- add "IProduct" interface in "product.types.ts" file
- add limit to products in "getProducts" route in "products.route.ts" file and same in "categories" section display only 8
1527. replace fetch with axios
- why i will use axios instead of fetch?
    . reponse in axios parse as json by default and i can get the data from the response vice versa fetch we need to parse the data to json()
    . axios provide eror object as axiosError and we can get the error message from it and this is better for ts
    . axios have many other features like interceptors, timeout, cancel token, etc
    . the best thing is "interceptors" which is a function that can be used to intercept the request and response and modify it before sending it to the server and after receiving the response from the server like adding headers, adding cookies, etc and in fetch we need to repeate the code in every request
    => like this
        - axios.interceptors.request.use((config) => {
            config.headers.Authorization = `Bearer ${localStorage.getItem("token")}`;
            return config;
        });

- create "axios.ts" file in "api" folder and add axios instance in it
- add "axiosInstance" in "axios.ts" file
- add axios interceptors in "axios.ts" file to add authorization header and handle 401 error "request, response"
- replace fetch with axios in "Login.tsx" and "Signup.tsx" file
1528. Add category filter in "categories" section
- add catgories route in "products.route.ts" file
- add "getCategories" function in "products.controller.ts" file use 'distinct' method to get all categories
- i create categories route in products route because for now i will use categories without manage it separately
if i manage it separately i will need to create a new route for categories and controllers for crud operations and add it in admin dashboard so for now to keep it simple i will use categories route in products route
- use catgories route in "Categories.tsx" component
- i convert all capital letters to lower case in categories enum in 'product.model.ts' file and also convert cateogry from user to lowercase before sending it to the database also in "AddProductForm" in "ProductForm.tsx" in "admin" folder
- add "useState" in "Categories.tsx" to save the category filter
- add "handleFilter" function in "Categories.tsx" to handle the category filter and update the category in the url
- the filter was taking alot of time to load filtered product when i choose category so i was forgot to add 'category' key in useQuery in "Categories.tsx" file
- now the filter is working fine
- add "All" button in "Categories.tsx" to handle the category filter and add onlcik event to it the value is null and set the category to null
1529. Add 'Profile" page when click on user icon in header after login
- i create 'Profile" page
- move to backend to add profile route and controller
- add profile page design and logic
- add for now 'random' avatar from 'pvatar.cc' for the first time user loged in and after that he can change it in 'edit profile' page
- add 'edit profile' form and logic using "dasyUI" modal
- add logic in backend as "editProfile" function in "users.controller.ts" file
- add post request type 'profile' route in "users.route.ts" file
- in logic editProfile function i use "findByIdAndUpdate" method to update the user in the database i faced a problem in this method "findByIdAndUpdate" method it display the old data before updating it so i used "new: true" to display the new data directly but it actually update the data it's only displaying the old data
1530. handle header routes when open another page and try to back to home page or any other page
- remove button and onclick event in "Header" component nav items
- use "Link" instead of "button"
- add '/#' to path in navItems to redirect to home page
- extract "hash" from "useLocation" to get "id" of sections
- add "useEffect" to setTimeout to scroll to the section with smooth way
1531. Hnadle refresh auto after logged in to dispaly user icon instead of not logged in button in header
- I was just checking localStorage—if a token exists, show the user icon; otherwise, show the login button.
- But even after logging in, the login button still shows, and I have to refresh manually for the user icon to appear.
    because localStorage is basically a global key store separate from react's rendering system when writing to it dosent notify react

    because react only re-renders when state/props/context chagne

    So localStorage alone is not enough to make your UI update dynamically.

- So I need i will make ui depends on client data if it exist then show user icon else show login button
- I will use queryClient to fetch data from the server and update the client data
- i use 'users/me' route in "Header" component to fetch data from the server
- i use 'client' queryKey in "Header" component to fetch data from the server
- i use 'enabled: !!token' in "Header" component to fetch data from the server only if token is not null
1532. solve unrefreshed header after logout
- the problem is when i logout the header is not refreshed and user icon still appears not logged in
- as the same thing in login and update header after login we use react query to rerender the header when the token is not null and the client data is not null
- her also we can't remove the token from localStorage and the login button will still appear after logout
- so in "Profile" handleLogout function
    - remove token from localStorage
    - then set client data to null
    - then remove queries with profile key
        and here i asked question why we remove queries with profile key?
            - because navagte outside profile page and profile unmounted anyway so why we keep it
    - then navigate to home page

- now logout logic is working fine

- move to "Header" component
    - add 'hasToken' variable to check if the token is not null every time the component renders
    - if the token is not null then display the user icon and cart icon
    - else display the login button
1533. Edit profile not updated momently when udpate profile first and last name in "Profile" page
- i faced a problem in "Profile" page when i update the first and last name in the form the data is not udpated in UI
- i found method called "invalidateQueries" in "useQueryClient" which is used to mark data as stale and refetch it need to be refetched from the server to ensure that the data is up to date
1534. Work on "ProductDetails" page
- get id from url using "useParams" hook and this is the first time i realize that we can't use hook with async function
- i use "useQuery" to fetch product data from the server
- how to hidden arrows in number button for 'input type number' add "input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {-webkit-appearance: none; margin: 0;}" in "index.css" file
- dispaly items count and add +, - logic
- active add to cart button
1535. stop daisy ui for theme switch
- add theme false in daisyUI plugin in 'index.css' file
- add theme switcher using 'context'
- create 'themeContext.ts' and themeProvider.tsx file in 'context' folder
- add '@custom-variant dark (&:where(.dark, .dark *));' in 'index.css' file
1536. Enhance "Categories" Section because it very heavy when loading
- create 'product.ts' in api folder to fetch products
- create 'categories.ts' in api folder to fetch categories
- enhance 'Categories' page to use 'product.ts' and 'categories.ts' to fetch data
- add 'placeholderData' in 'useQuery' to keep previous data when fetching data untill new data is available
- create 'CategoriesFilter' component to handle filter in 'Categories' section
- add 'Suspense' in 'Home' page
- add memo to both 'ProductCard' and 'CategoriesFilter' components
1537. Add 'add to cart' logic to the server
- create 'cart.model'
    - add 'productId' which reference to 'Product' model
    - also add 'userId' which reference to 'User' model
    - add 'quantity' which is the quantity of the product in the cart
    - i add 'cartItem' interface which contain productId and quantity
    - after that i add 'ICart' interface which contain userId and items which is an array of 'cartItem'
    - then in 'cartSchema' i add userId and 'items' which is an array of 'cartItem'
    - why i didn't add 'price' and 'total' in the model?
        - because both will be calculated by the server
- create 'cart.controller'
    - add 'getCart' route to get all cart items
    - add 'addToCart' route to add product to cart
        - for just now i will only allow add to cart for just logged in user
        - i will send only 'productId' and 'quantity' in the request body
        - in 'addToCart' logic i first get 'productId' and 'quantity' from the request body
        - then i get 'userId' from the request header because i want to add product to cart only for logged in user
        - then i check if the 'productId' exist
        - check if there is any product in the cart for the user
        - if there is no any products then create a new cart for the user by using 'create' method and add 'userId' and 'products' in the cart
        - why send userId in the cart?
            - becuase every cart will be assigned to a user
        - last push the product to the cart
            - if the product exist in the cart then add the quantity to the product
            - else push the product to the cart
        - i faced an error the productId was added to the cart as separate products and the quantity not added to the exist product
            - after change productId to mongoose.Types.ObjectId i can add the quantity to the exist product
            - becuase the type of productId in cart.model i set it as mongoose.Types.ObjectId
            - and productId i get from request body is string so i convert it to mongoose.Types.ObjectId
            - why i didn't use === '' to compare the productId?
                - because rule of js Equality doesn't work with object
                - so i use .equals() method to compare between objects
                - and this is source https://www.codemzy.com/blog/compare-mongodb-objectid
1529. work on 'removeFromCart' logic in 'cart.controller.ts' file
- get userId from request header
- get productId from request params
- use 'findOneAndDelete' method to remove the product from the cart and don't forget to convert productId to mongoose.Types.ObjectId
- but if you use findOneAndDelete method it will delete the whole cart of this user so
- we have to use method only remove clicable product
- 'pull' operator is used to remove the item from the array
- and now we will use findOneAndUpdate method to update the cart and remove the product from the cart by using 'pull' operator
- add 'removeFromCart' mutation in 'Cart' page
1530. work on 'clearCart' logic in 'cart.controller.ts' file
- use findOneAndUpdate method to remove all cart items for the user and set products to empty array
- and don't forget to use new: true to update the document
1531. work on 'updateCartItem' logic in 'cart.controller.ts' file
- server
    - get userId from request header
    - get productId and quantity from request body
    - i miss understand what 'updteCartItem' logic is i need to study mongodb operators
- client
    - add 'updateCartItem' mutation in 'Cart' page
    - and don't forget to use invalidateQueries to invalidate the cache of the 'cart' query and refetch it to update the cart momentely and cartCount
1532. Work on 'productCard' component in 'Categories' section
- add 'add to cart' logic
1532. work on 'Cart' page in 'Cart.tsx' file
- display cart items
- add ICartItem interface
- add ICart interface
1533. back to server 'getCart' logic in 'cart.controller.ts' file
- i found hard to get the data from the cart in 'Cart' page in front because i was use 'find' method which fetch all data from the cart for different users
- so i use 'findOne' return only single document that matches the query if not exist return null

- what is the difference between 'findOne' and 'find' method?
    - findOne return only one document that match the query
    - find return all documents that match the query

- then i use 'populate' method to repalce references stored as ObjectId with actual document they references from another collection
- add subtotal and total in 'Cart' page order summary
    by using reduce method to calculate the subtotal and total
1534. Add 'ClearCart' in 'Cart' page
- use mutation to clear the cart
- use 'invalidateQueries' to invalidate the cache of the 'cart' query and refetch it to update the cart momentely
1535. Add cart count number in 'Header'
- server
    - add 'getCartCount' route
    - add 'getCartCount' function in 'cart.controller.ts'
        - use 'findOne' method to get the cart from the database
        - use 'select' method to get only the products from the cart
        - then use 'length' method to get the length of the products array
        - return the count of the products and the items in the cart
        - which
            count: is the the sum of the quantity of the products
            items: is the length of the products array "different products'
- client
    - add useQuery to fetch the cart count
    - but the number is not updated when the cart is updated
    - i use invalidateQueries to invalidate the cache of the cartCount query and refetch it to update the cart count momentely but=>
        but => ال component الل بيعرض data عمره ما يعمل invalidate لنفسه
        means the component who's display the data will not invalidate the cache of the query by itself
        - so back to 'add to cart' logic and add 'invalidateQueries' to the 'cart-count' mutation in 'ProductCard' component
1536. I faced a problem in 'Profile' page when i logout
- when i logout the token is not removed from localStorage successfully but the user icon still appears in the header
- i back again to 'Profile' page in 'handleLogout' function and use 'queryClient.removeQueries' to remove the queries with 'client' key
- also use 'queryClient.removeQueries' to remove the queries with 'cartCount' key to make only number of products in cart appears only when the user logged in
1537. Enahance 'Categories' section
- add 'productCardSkeleton' instead of 'loading' when fetching data
- we were use parallel fetching for the products and categories so loading is so heavy
how to make it better?
    - first i use 'isLoadingProducts' and display array of 'ProductCardSkeleton' when it's true
    - then i use 'staleTime' in 'useQuery' in 'categories' to make it refresh every 10 minutes means will get the data from the cache until the 10 minutes passed
    - in 'home' page i use 'lazy' to load 'Categories' component
    - use 'useSuspenseQuery' instead of 'useQuery' in 'Categories' and 'Products' 
    - 



sources:
    - https://www.developerway.com/posts/how-to-fetch-data-in-react
    - https://freedium-mirror.cfd/https://medium.com/@ThinkingLoop/12-react-19-data-fetching-patterns-that-kill-waterfalls-6782ef923fc0
    - https://tanstack.com/query/v5/docs/framework/react/guides/request-waterfalls
    -

ProductDetails udpate cart +, - hid limited time offers for now footer understand mongodb operators understand updateCartItem logic in server

  • enhance categories section heavy loading
  • add loading when fetching data
  • add not found page
  • add 'LimitedTimeOffers.tsx' section
  • add 'CountdownTimer.tsx' in separate component
  • enhance colors
  • add tag in product card in backend

client

Hero
- subtitle text color not work netural-light

add change password logic in "Profile" page

add limited time offers in "Home" page

add countdown timer in "Home" page

add "not found" page

add "loading" when fetching data

add tag in product card in backend

add loading when fetching data

add categories route separately

dark theme problems

server

test if cloudinary is working by console.log(cloudinary.config()) using cloudinary url

admin

. Add gloabal fetcher to prevent repeated code
when ErrorBoundary error component appears i can't test it
handle profile icon styles and display mail

add delete all product and users in "Users" page and "Products" page

enhance shape of formData in "ProductForm" instead of write each line of formData

add dark mode in dashboard

add skeleton when loading

add loading, and error message when fetching data

add not found page

use axios or fetch

70. How to handle password field in 'Add User' page

client

35. Use Timer to show the countdown

30. There is a problem in text in dark in shop now button in hero section and bg in dark theme in learn more button in hero section

31. There is a problem in border of header, footer

22. Add hover effect to CartIcon.tsx

23 Make title of sections dynamic

Future features