- React
- React-router
- React-query or tanstack (for data fetching)
- TailwindCSS
- TypeScript
- react-toastify
- shadcn
- lucide-react
- react-hook-form
- zod
- Node.js
- Express
- MongoDB
- Mongoose
- TypeScript
- cors
- bycrypt
- dotenv
- jsonwebtoken
- express-validator
- nodemon
- multer
- cloudinary
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
23. Enhance "Header" by adding links to array and loop through the links, and use 'memo' to optimize performance
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
40. Install "express", "mongoose" "jsonwebtoken", "nodemon", 'cors', 'bycrypt and "dotenv" using npm
50. Get the code of customer management dashboard from my previous project "customer-management-react-node"
52. Update "Home" and replace 'customer' with 'products' to display total count of 'users' and 'products'
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
62. If i want 'fullName' appears when send data to frontend then i need to add 'toJSON' and 'toObject' 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
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
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
79. Add main titles in products table 'Name', 'Category', 'Price', 'Image', 'Description', 'inStock'
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
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
- 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
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
i solved it by just restarting the laptop
97. To convert date retrieved from database to date format in frontend use "toLocaleDateString()" method
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
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
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
- 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
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
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
- فولدر ال lib ده الل بنحط فيه الخدمات الأساسية الل بنربط بيها خدمات خارجيه أو أنظمه جوا المشروع زى db connection, stripe setup, cloudinary setup, etc.
- فولدر ال utlis ده لل functions الصغيره الل بستخدمها أكتر من مرة
1- Express م بيعرف يستقبل ملفات لو بعتله صوره هتوصله بصيغه multipart/form-data و express م فاهمها ف بنستخدم multer library ويفكه
2- استخدمنا memory storage نحظ الصوره على الرام على شكل buffer وبعدين نرفعها على cloudinary
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
- 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
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
- 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?
- 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
- 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
- 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
- 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"
- 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
-
- 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 لازم يبقى واحد من دول
- 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
- i add protected route in "AppRouter.tsx" file
- i create 'isAuthenticated' function in "utils" folder which check if the token is in local storage
- 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
- 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
1512. add count routes for both products and users to return the count of products and users using "countDocuments"
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
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
- 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
- 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
-
ال 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
- لأنها زى ما بنقول هى مجرد function بننادى عليها وبتخلص
- وملهاش instance and this بالتالى مافيش مكان لل lifecycle methods
- جابوا ال 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 لكده
- استخدام ال 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- add 'add to cart' logic
- display cart items
- add ICartItem interface
- add ICart interface
- 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
- use mutation to clear the cart
- use 'invalidateQueries' to invalidate the cache of the 'cart' query and refetch it to update the cart momentely
- 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
- 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
- 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
- subtitle text color not work netural-light