From 1f4ad42748c5817c499e52c8dfde6f3fe15ccd34 Mon Sep 17 00:00:00 2001 From: builtbysuraj Date: Sat, 20 Jan 2024 22:52:35 +0530 Subject: [PATCH 01/19] feat: Add useSearchParams for filter and URL params --- client/src/hooks/useFilters.ts | 41 ++++----- client/src/hooks/useGetParams.ts | 12 +++ client/src/hooks/useHandleDispatch.ts | 44 +++++++--- client/src/layouts/filters/SidebarFilters.tsx | 29 +++--- .../filters/components/CategoryFilter.tsx | 88 ++++++++++++------- .../filters/components/PriceSliderFilter.tsx | 19 +++- .../filters/components/RatingFilter.tsx | 16 +++- .../filters/components/SortProductsFilter.tsx | 13 ++- client/src/layouts/header/Header.module.css | 2 +- client/src/layouts/header/Header.tsx | 7 +- client/src/pages/home/HomePage.module.css | 7 ++ client/src/pages/home/HomePage.tsx | 6 +- .../product-listing/ProductListingPage.tsx | 8 +- client/src/state/services/productApi.ts | 2 +- client/src/state/slices/filtersSlice.ts | 7 +- client/tsconfig.json | 1 + 16 files changed, 205 insertions(+), 97 deletions(-) create mode 100644 client/src/hooks/useGetParams.ts diff --git a/client/src/hooks/useFilters.ts b/client/src/hooks/useFilters.ts index 65c8dbb..6779e41 100644 --- a/client/src/hooks/useFilters.ts +++ b/client/src/hooks/useFilters.ts @@ -1,37 +1,38 @@ import { SORT_TYPE } from '@/constants/filterConstants' import { useGetAllProductsQuery } from '@/state/services/productApi' import { useAppSelector } from '@/state/store' +import { ProductType } from '@/types' +import useGetParams from './useGetParams' export default function useFilter() { const stateData = useAppSelector((state) => state.filter) - const { data } = useGetAllProductsQuery() - const filteredData = data?.products - ?.filter((item) => { + const { data, isLoading } = useGetAllProductsQuery(null) + + const { q, category, rating, price } = useGetParams() + + const products = data?.products + + const filteredData = products + ?.filter((item: ProductType) => { // Price filter - if ( - stateData.priceRange && - !( - item.price >= stateData.priceRange[0] && - item.price <= stateData.priceRange[1] - ) - ) + if (price && !(item.price >= price[0] && item.price <= price[1])) return false // Rating filter - if ( - stateData.stateRating && - !(item.rating >= Number(stateData.stateRating)) - ) - return false + if (rating && !(item.rating >= Number(rating))) return false // Search filter if ( - stateData.searchQuery && - !item.title.toLowerCase().includes(stateData.searchQuery) && - !item.category.toLowerCase().includes(stateData.searchQuery) && - !item.brand.toLowerCase().includes(stateData.searchQuery) + q && + !item.title.toLowerCase().includes(q.toLowerCase()) && + !item.category.toLowerCase().includes(q.toLowerCase()) && + !item.brand.toLowerCase().includes(q.toLowerCase()) ) return false + + // category filter + if (category && !(item.category === category)) return false + // Add more filters as needed return true }) @@ -49,5 +50,5 @@ export default function useFilter() { } }) - return { filteredData } + return { filteredData, isLoading } } diff --git a/client/src/hooks/useGetParams.ts b/client/src/hooks/useGetParams.ts new file mode 100644 index 0000000..cb21a11 --- /dev/null +++ b/client/src/hooks/useGetParams.ts @@ -0,0 +1,12 @@ +import { useSearchParams } from 'react-router-dom' + +export default function useGetParams() { + const searchParams = useSearchParams()[0] + + const q = searchParams.get('q') || '' + const category = searchParams.get('category') || '' + const rating = searchParams.get('rating') || '' + const price = searchParams.get('price')?.split('-').map(Number) || [10, 2000] + + return { category, price, q, rating } +} diff --git a/client/src/hooks/useHandleDispatch.ts b/client/src/hooks/useHandleDispatch.ts index 2f8e76d..6d978d9 100644 --- a/client/src/hooks/useHandleDispatch.ts +++ b/client/src/hooks/useHandleDispatch.ts @@ -1,27 +1,31 @@ +import { useSearchParams } from 'react-router-dom' + import { addToCart, decrementCartItem, removeFromCart, } from '@/state/slices/cartSlice' -import { - clearFilters, - filterRating, - filterSearch, - priceRange, - sort, -} from '@/state/slices/filtersSlice' +import { sort } from '@/state/slices/filtersSlice' import { useAppDispatch } from '@/state/store' import { CartType } from '@/types' export default function useHandleDispatch() { const dispatch = useAppDispatch() + const setSearchParams = useSearchParams()[1] const handlePriceRange = (event: Event, newValue: number | number[]) => { - dispatch(priceRange(newValue)) + setSearchParams((prev) => { + // @ts-expect-error - MUI's fault? + prev.set('price', `${newValue[0]}-${newValue[1]}`) + return prev + }) } const handleFilterRating = (filterValue: string) => { - dispatch(filterRating(filterValue)) + setSearchParams((prev) => { + prev.set('rating', filterValue) + return prev + }) } const handleSort = (sortType: string) => { @@ -29,7 +33,10 @@ export default function useHandleDispatch() { } const handleSearchQuery = (query: string) => { - dispatch(filterSearch(query)) + setSearchParams((prev) => { + prev.set('q', query) + return prev + }) } const handleAddToCart = (data: CartType) => { @@ -44,8 +51,22 @@ export default function useHandleDispatch() { dispatch(removeFromCart(cartItemId)) } + const handleCategoryFilter = (category: string) => { + setSearchParams((prev) => { + prev.set('category', category) + prev.delete('q') + return prev + }) + } + const handleClearFilter = () => { - dispatch(clearFilters()) + setSearchParams((prev) => { + prev.delete('q') + prev.delete('category') + prev.delete('rating') + prev.delete('price') + return prev + }) } return { @@ -56,6 +77,7 @@ export default function useHandleDispatch() { handleRemoveFromCart, handlePriceRange, handleSearchQuery, + handleCategoryFilter, handleClearFilter, } } diff --git a/client/src/layouts/filters/SidebarFilters.tsx b/client/src/layouts/filters/SidebarFilters.tsx index d414506..af084c1 100644 --- a/client/src/layouts/filters/SidebarFilters.tsx +++ b/client/src/layouts/filters/SidebarFilters.tsx @@ -1,14 +1,15 @@ -import { Box, Button, FormControl, Typography } from '@mui/material' +import { Box, FormControl, Typography } from '@mui/material' +import { useSearchParams } from 'react-router-dom' import useHandleDispatch from '@/hooks/useHandleDispatch' -import { useAppSelector } from '@/state/store' +import CategoryFilter from './components/CategoryFilter' import PriceSliderFilter from './components/PriceSliderFilter' -import SortProductsFilter from './components/SortProductsFilter' import RatingFilter from './components/RatingFilter' -import CategoryFilter from './components/CategoryFilter' +import SortProductsFilter from './components/SortProductsFilter' export default function SidebarFilters() { - const stateData = useAppSelector((state) => state.filter) + // const stateData = useAppSelector((state) => state.filter) + const searchParams = useSearchParams()[0] const { handleFilterRating, handleSort, @@ -22,29 +23,33 @@ export default function SidebarFilters() { Filters + {/* Clear Filters */} - +
+ {/* Price Filter */} + {/* Sort products */} - + {/* Ratings filter */} {/* Category Filters */} - +
) diff --git a/client/src/layouts/filters/components/CategoryFilter.tsx b/client/src/layouts/filters/components/CategoryFilter.tsx index 2f3f041..d976beb 100644 --- a/client/src/layouts/filters/components/CategoryFilter.tsx +++ b/client/src/layouts/filters/components/CategoryFilter.tsx @@ -1,42 +1,70 @@ -import { Checkbox, FormControlLabel, FormGroup } from '@mui/material' +import useHandleDispatch from '@/hooks/useHandleDispatch' +import { FormControlLabel, Radio, RadioGroup, Typography } from '@mui/material' -const categories = [ - 'smartphones', - 'laptops', - 'fragrances', - 'skincare', - 'groceries', - 'home-decoration', - 'furniture', - 'tops', - 'womens-dresses', - 'womens-shoes', - 'mens-shirts', - 'mens-shoes', - 'mens-watches', - 'womens-watches', - 'womens-bags', - 'womens-jewellery', - 'sunglasses', - 'automotive', - 'motorcycle', - 'lighting', -] +const data = { + Smartphones: 'smartphones', + Laptops: 'laptops', + Fragrances: 'fragrances', + Skincare: 'skincare', + Groceries: 'groceries', + 'Home Decoration': 'home-decoration', + Furniture: 'furniture', + Tops: 'tops', + 'Womens Dresses': 'womens-dresses', + 'Womens Shoes': 'womens-shoes', + 'Mens Shirts': 'mens-shirts', + 'Mens Shoes': 'mens-shoes', + 'Mens Watches': 'mens-watches', + 'Womens Watches': 'womens-watches', + 'Womens Bags': 'womens-bags', + 'Womens Jewellery': 'womens-jewellery', + Sunglasses: 'sunglasses', + Automotive: 'automotive', + Motorcycle: 'motorcycle', + Lighting: 'lighting', +} + +type Props = { + searchParams: URLSearchParams +} + +export default function CategoryFilter({ searchParams }: Props) { + // const formattedCategories = categories.map((category) => + // category.includes('-') + // ? category + // .split('-') + // .map( + // (splitedWord) => + // splitedWord.charAt(0).toUpperCase() + splitedWord.slice(1) + // ) + // .join(' ') + // : category.charAt(0).toUpperCase() + category.slice(1) + // ) + // console.log(formattedCategories) + const { handleCategoryFilter } = useHandleDispatch() + const category = searchParams.get('category') || '' -export default function CategoryFilter() { return ( <> - {/* @ts-expect-error - mui component issue */} - console.log(e.target.value)}> - {categories.map((category, index) => ( + + Categories + + handleCategoryFilter(e.target.value)} + > + {Object.keys(data).map((category, index) => ( } + control={} label={category} /> ))} - + ) } diff --git a/client/src/layouts/filters/components/PriceSliderFilter.tsx b/client/src/layouts/filters/components/PriceSliderFilter.tsx index f27b841..11e85cf 100644 --- a/client/src/layouts/filters/components/PriceSliderFilter.tsx +++ b/client/src/layouts/filters/components/PriceSliderFilter.tsx @@ -1,16 +1,27 @@ import { Box, Slider, Typography } from '@mui/material' -export default function PriceSliderFilter({ stateData, handlePriceRange }) { +type Props = { + searchParams: URLSearchParams + handlePriceRange: (arg0: Event, arg1: number | number[]) => void +} + +export default function PriceSliderFilter({ + searchParams, + handlePriceRange, +}: Props) { + const price = searchParams.get('price')?.split('-').map(Number) || [10, 2000] + return ( <> Price - ${stateData.priceRange[0]} - ${stateData.priceRange[1]} +
+ {`$${price[0]} - $${price[1]}`}
void +} + +export default function RatingFilter({ + searchParams, + handleFilterRating, +}: Props) { + const rating = searchParams.get('rating') || '' -export default function RatingFilter({ stateData, handleFilterRating }) { return ( <> @@ -10,7 +20,7 @@ export default function RatingFilter({ stateData, handleFilterRating }) { handleFilterRating(e.target.value)} > void +} -export default function SortProductsFilter({ stateData, handleSort }) { +export default function SortProductsFilter({ searchParams, handleSort }: Props) { return ( <> Sort - } label="Rating High To Low" /> - + */} ) } diff --git a/client/src/layouts/header/Header.module.css b/client/src/layouts/header/Header.module.css index 833d588..8b969b5 100644 --- a/client/src/layouts/header/Header.module.css +++ b/client/src/layouts/header/Header.module.css @@ -4,7 +4,7 @@ display: flex; align-items: center; justify-content: center; - min-width: 100rem; + min-width: 100%; /* position: sticky; */ } .header-container a { diff --git a/client/src/layouts/header/Header.tsx b/client/src/layouts/header/Header.tsx index 2e7f63f..d951673 100644 --- a/client/src/layouts/header/Header.tsx +++ b/client/src/layouts/header/Header.tsx @@ -1,4 +1,4 @@ -import { Link } from 'react-router-dom' +import { Link, useSearchParams } from 'react-router-dom' import cart from '@/assets/img/cart.svg' import search from '@/assets/img/search.svg' @@ -11,9 +11,10 @@ import flipkart from '/flipkart.png' const cx = classNames.bind(styles) export default function Header() { - const stateData = useAppSelector((state) => state.filter) const cartData = useAppSelector((state) => state.cart) + const searchParams = useSearchParams()[0] const { handleSearchQuery } = useHandleDispatch() + const q = searchParams.get('q') || '' return (
@@ -27,7 +28,7 @@ export default function Header() { handleSearchQuery(e.target.value)} /> +
HomePage
Product Listing Cart - - +
) } diff --git a/client/src/pages/product-listing/ProductListingPage.tsx b/client/src/pages/product-listing/ProductListingPage.tsx index ea6798a..d3400bf 100644 --- a/client/src/pages/product-listing/ProductListingPage.tsx +++ b/client/src/pages/product-listing/ProductListingPage.tsx @@ -1,13 +1,15 @@ import { Link } from 'react-router-dom' -import Box from '@mui/material/Box' -import Paper from '@mui/material/Paper' import useFilter from '@/hooks/useFilters' import { SidebarFilters } from '@/layouts' import { ProductType } from '@/types' +import Box from '@mui/material/Box' +import Paper from '@mui/material/Paper' export default function ProductListingPage() { - const { filteredData } = useFilter() + const { filteredData, isLoading } = useFilter() + + if (isLoading) return

Loading....

return (
diff --git a/client/src/state/services/productApi.ts b/client/src/state/services/productApi.ts index 7363fe3..9dd032a 100644 --- a/client/src/state/services/productApi.ts +++ b/client/src/state/services/productApi.ts @@ -7,7 +7,7 @@ export const productApi = createApi({ baseQuery: fetchBaseQuery({ baseUrl: ENV.API_BASE_URL }), tagTypes: ['product'], endpoints: (builder) => ({ - getAllProducts: builder.query({ + getAllProducts: builder.query({ query: () => '/products', providesTags: ['product'], }), diff --git a/client/src/state/slices/filtersSlice.ts b/client/src/state/slices/filtersSlice.ts index c292d86..cdc7dfc 100644 --- a/client/src/state/slices/filtersSlice.ts +++ b/client/src/state/slices/filtersSlice.ts @@ -7,6 +7,7 @@ type InitialStateType = { stateRating: string | null priceRange: number[] searchQuery: string + category: string } export const initialState: InitialStateType = { @@ -15,6 +16,7 @@ export const initialState: InitialStateType = { stateRating: null, priceRange: [10, 2000], searchQuery: '', + category: '' } const filtersSlice = createSlice({ @@ -33,13 +35,16 @@ const filtersSlice = createSlice({ filterSearch(state, action) { state.searchQuery = action.payload }, + categoryFilter(state, action) { + state.category = action.payload + }, clearFilters() { return initialState }, }, }) -export const { filterRating, sort, priceRange, filterSearch, clearFilters } = +export const { filterRating, sort, priceRange, filterSearch, categoryFilter ,clearFilters } = filtersSlice.actions export default filtersSlice.reducer diff --git a/client/tsconfig.json b/client/tsconfig.json index 8b4a93d..fc2d12f 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -22,6 +22,7 @@ } }, "include": ["src"], + "exclude": ["node_modules"], "references": [ { "path": "./tsconfig.node.json" From 3af1d7f51690d76333fc14fabdc74c8e5930a448 Mon Sep 17 00:00:00 2001 From: builtbysuraj Date: Sun, 21 Jan 2024 17:56:46 +0530 Subject: [PATCH 02/19] feat: Add sort filter, Add husky --- .gitignore | 2 + .husky/pre-commit | 45 +++++++++++++++++ client/package.json | 2 + client/src/constants/filterConstants.ts | 1 - client/src/hooks/useFilters.ts | 9 ++-- client/src/hooks/useGetParams.ts | 3 +- client/src/hooks/useHandleDispatch.ts | 11 +++-- client/src/layouts/filters/SidebarFilters.tsx | 1 - .../filters/components/SortProductsFilter.tsx | 49 ++++++++++++------- client/src/main.tsx | 6 ++- client/src/pages/login/LoginPage.module.css | 6 +-- client/src/pages/login/LoginPage.tsx | 21 ++++---- .../product-details/ProductDetailsPage.tsx | 12 +++-- client/src/pages/signup/SignUpPage.module.css | 6 +-- client/src/pages/signup/SignUpPage.tsx | 21 ++++---- client/src/state/slices/cartSlice.ts | 2 +- client/src/utils/index.ts | 6 +++ client/vite.config.ts | 42 +++++++++++----- package-lock.json | 27 ++++++++++ package.json | 12 +++++ server/package.json | 3 +- 21 files changed, 205 insertions(+), 82 deletions(-) create mode 100644 .gitignore create mode 100644 .husky/pre-commit create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..bc92427 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,45 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +echo ' +💅🛠️ Formatting and testing project before committing... +' + +# Check tsconfig standards +npm run check-types || +( + echo ' + TypeScript Check Failed. ❌ + Make the required changes listed above, add changes and try to commit again. + ' + false; +) + + +# Check ESLint Standards +npm run check-lint || +( + echo ' + ESLint Check Failed. ❌ + Make the required changes listed above, add changes and try to commit again. + ' + false; +) + + +# Check Prettier standards +npm run format-client || +( + echo ' + Prettier Check Failed. ❌ + Run npm run format, add changes and try commit again.'; + false; +) + +npm run format-server || +( + echo ' + Prettier Check Failed. ❌ + Run npm run format, add changes and try commit again.'; + false; +) diff --git a/client/package.json b/client/package.json index d420521..cfd6caa 100644 --- a/client/package.json +++ b/client/package.json @@ -8,6 +8,8 @@ "build": "vite build", "preview": "vite preview", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "check-lint": "eslint . --ext ts,tsx", + "check-types": "tsc --pretty --noEmit", "test": "vitest", "format": "prettier --write ./src" }, diff --git a/client/src/constants/filterConstants.ts b/client/src/constants/filterConstants.ts index 8ec39d0..1365d75 100644 --- a/client/src/constants/filterConstants.ts +++ b/client/src/constants/filterConstants.ts @@ -2,7 +2,6 @@ export const SORT_TYPE = { PRICE_HIGH_TO_LOW: 'priceHighToLow', PRICE_LOW_TO_HIGH: 'priceLowToHigh', RATING_HIGH_TO_LOW: 'ratingHighToLow', - RATING_LOW_TO_HIGH: 'ratingLowToHigh', } export const RATING_TYPE = { diff --git a/client/src/hooks/useFilters.ts b/client/src/hooks/useFilters.ts index 6779e41..2e55f2e 100644 --- a/client/src/hooks/useFilters.ts +++ b/client/src/hooks/useFilters.ts @@ -1,15 +1,12 @@ import { SORT_TYPE } from '@/constants/filterConstants' import { useGetAllProductsQuery } from '@/state/services/productApi' -import { useAppSelector } from '@/state/store' import { ProductType } from '@/types' import useGetParams from './useGetParams' export default function useFilter() { - const stateData = useAppSelector((state) => state.filter) const { data, isLoading } = useGetAllProductsQuery(null) - const { q, category, rating, price } = useGetParams() - + const { q, category, rating, price, sort } = useGetParams() const products = data?.products const filteredData = products @@ -36,8 +33,8 @@ export default function useFilter() { // Add more filters as needed return true }) - .sort((a, b) => { - switch (stateData.sort) { + .sort((a: ProductType, b: ProductType) => { + switch (sort) { case SORT_TYPE.PRICE_HIGH_TO_LOW: return b.price - a.price case SORT_TYPE.PRICE_LOW_TO_HIGH: diff --git a/client/src/hooks/useGetParams.ts b/client/src/hooks/useGetParams.ts index cb21a11..66ba33e 100644 --- a/client/src/hooks/useGetParams.ts +++ b/client/src/hooks/useGetParams.ts @@ -7,6 +7,7 @@ export default function useGetParams() { const category = searchParams.get('category') || '' const rating = searchParams.get('rating') || '' const price = searchParams.get('price')?.split('-').map(Number) || [10, 2000] + const sort = searchParams.get('sort') - return { category, price, q, rating } + return { category, price, q, rating, sort } } diff --git a/client/src/hooks/useHandleDispatch.ts b/client/src/hooks/useHandleDispatch.ts index 6d978d9..f78dcfb 100644 --- a/client/src/hooks/useHandleDispatch.ts +++ b/client/src/hooks/useHandleDispatch.ts @@ -5,9 +5,8 @@ import { decrementCartItem, removeFromCart, } from '@/state/slices/cartSlice' -import { sort } from '@/state/slices/filtersSlice' import { useAppDispatch } from '@/state/store' -import { CartType } from '@/types' +import { CartType, ProductType } from '@/types' export default function useHandleDispatch() { const dispatch = useAppDispatch() @@ -29,7 +28,10 @@ export default function useHandleDispatch() { } const handleSort = (sortType: string) => { - dispatch(sort(sortType)) + setSearchParams((prev) => { + prev.set('sort', sortType) + return prev + }) } const handleSearchQuery = (query: string) => { @@ -39,7 +41,7 @@ export default function useHandleDispatch() { }) } - const handleAddToCart = (data: CartType) => { + const handleAddToCart = (data: ProductType) => { dispatch(addToCart(data)) } @@ -65,6 +67,7 @@ export default function useHandleDispatch() { prev.delete('category') prev.delete('rating') prev.delete('price') + prev.delete('sort') return prev }) } diff --git a/client/src/layouts/filters/SidebarFilters.tsx b/client/src/layouts/filters/SidebarFilters.tsx index af084c1..352a275 100644 --- a/client/src/layouts/filters/SidebarFilters.tsx +++ b/client/src/layouts/filters/SidebarFilters.tsx @@ -8,7 +8,6 @@ import RatingFilter from './components/RatingFilter' import SortProductsFilter from './components/SortProductsFilter' export default function SidebarFilters() { - // const stateData = useAppSelector((state) => state.filter) const searchParams = useSearchParams()[0] const { handleFilterRating, diff --git a/client/src/layouts/filters/components/SortProductsFilter.tsx b/client/src/layouts/filters/components/SortProductsFilter.tsx index 5c5803b..54c2412 100644 --- a/client/src/layouts/filters/components/SortProductsFilter.tsx +++ b/client/src/layouts/filters/components/SortProductsFilter.tsx @@ -1,39 +1,52 @@ import { SORT_TYPE } from '@/constants/filterConstants' -import { FormControlLabel, Radio, RadioGroup, Typography } from '@mui/material' +import { Typography } from '@mui/material' type Props = { searchParams: URLSearchParams handleSort: (arg0: string) => void } -export default function SortProductsFilter({ searchParams, handleSort }: Props) { +export default function SortProductsFilter({ + searchParams, + handleSort, +}: Props) { + const sort = searchParams.get('sort') || '' return ( <> Sort - {/* handleSort(e.target.value)} - > - + } - label="Sort by high to low price" + onChange={(e) => handleSort(e.target.value)} + checked={sort === SORT_TYPE.PRICE_HIGH_TO_LOW} /> - Price high to low +
+ } - label="Sort by low to high price" + onChange={(e) => handleSort(e.target.value)} + checked={sort === SORT_TYPE.PRICE_LOW_TO_HIGH} /> - Price low to high +
+ } - label="Rating High To Low" + onChange={(e) => handleSort(e.target.value)} + checked={sort === SORT_TYPE.RATING_HIGH_TO_LOW} /> -
*/} +
+ ) } diff --git a/client/src/main.tsx b/client/src/main.tsx index 092f6d1..70dbe5b 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,14 +1,16 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { createRoot } from 'react-dom/client' import { Provider } from 'react-redux' import { RouterProvider } from 'react-router-dom' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import './assets/css/reset.css' import router from './routes' import store from './state/store' -if (process.env.NODE_ENV !== 'development') { +if (process.env.NODE_ENV === 'production') { console.log = () => {} + console.warn = () => {} + console.error = () => {} } const queryClient = new QueryClient() diff --git a/client/src/pages/login/LoginPage.module.css b/client/src/pages/login/LoginPage.module.css index 5993646..62aef7a 100644 --- a/client/src/pages/login/LoginPage.module.css +++ b/client/src/pages/login/LoginPage.module.css @@ -32,7 +32,7 @@ height: 100%; } -.right form { +.right .login-form { margin-top: 2rem; display: flex; align-items: center; @@ -40,7 +40,7 @@ gap: 0.8rem; } -form input { +.login-form input { width: 100%; padding: 0.4rem 1rem; margin: 0.6rem 0; @@ -53,7 +53,7 @@ input::placeholder { opacity: 0.7; } -form button { +.login-form button { border: 0; background-color: #fb641b; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2); diff --git a/client/src/pages/login/LoginPage.tsx b/client/src/pages/login/LoginPage.tsx index 3efc2db..498505c 100644 --- a/client/src/pages/login/LoginPage.tsx +++ b/client/src/pages/login/LoginPage.tsx @@ -1,35 +1,32 @@ -import classNames from 'classnames/bind' import { Link } from 'react-router-dom' import styles from './LoginPage.module.css' -const cx = classNames.bind(styles) - export default function LoginPage() { return ( -
-
-
+
+
+
Login
-
+

Get access to your Orders,

Wishlist and Recommendations

-
-
+
+ By continuing, you agree to Flipkart's - Terms of Use + Terms of Use and - Privacy Policy. + Privacy Policy.

Dont have an account?{' '} - + Signup

diff --git a/client/src/pages/product-details/ProductDetailsPage.tsx b/client/src/pages/product-details/ProductDetailsPage.tsx index 7b1236e..752e805 100644 --- a/client/src/pages/product-details/ProductDetailsPage.tsx +++ b/client/src/pages/product-details/ProductDetailsPage.tsx @@ -1,16 +1,18 @@ import { Link, useParams } from 'react-router-dom' -import { useState } from 'react' import useHandleDispatch from '@/hooks/useHandleDispatch' import { useGetProductByIdQuery } from '@/state/services/productApi' +import { useState } from 'react' export default function ProductDetailsPage() { - const [isAddedToCart, setIsAddedToCart] = useState(false) + const [isAddedToCart] = useState(false) const { id } = useParams() const { handleAddToCart } = useHandleDispatch() - const { data } = useGetProductByIdQuery(String(id)) - console.log(data) + const { data, isFetching } = useGetProductByIdQuery(String(id)) + + if (isFetching) return

Loading...

+ return (
@@ -28,7 +30,7 @@ export default function ProductDetailsPage() { <>

Already have an account?{' '} - + Login

diff --git a/client/src/state/slices/cartSlice.ts b/client/src/state/slices/cartSlice.ts index 2cecdcf..789d308 100644 --- a/client/src/state/slices/cartSlice.ts +++ b/client/src/state/slices/cartSlice.ts @@ -7,7 +7,7 @@ const cartSlice = createSlice({ name: 'cart', initialState, reducers: { - addToCart(state, action: PayloadAction) { + addToCart(state, action) { const item = state.find((item: CartType) => item.id === action.payload.id) if (item) { item.quantity += 1 diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts index c39ec4a..898d270 100644 --- a/client/src/utils/index.ts +++ b/client/src/utils/index.ts @@ -7,3 +7,9 @@ export const totalCartPrice = (cartData: CartType[]) => { } return cartPrice } + +type ClassesType = string | boolean | null | undefined + +export const cx = (...classes: ClassesType[]) => { + return classes.filter(Boolean).join(' ') +} diff --git a/client/vite.config.ts b/client/vite.config.ts index b89b1f9..0f39325 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -5,16 +5,34 @@ import react from '@vitejs/plugin-react' import path from 'path' import { defineConfig } from 'vite' -export default defineConfig({ - plugins: [react()], - resolve: { - alias: { - '@': path.resolve(__dirname, './src'), +type ViteConfigInput = { + mode: string + command: string +} + +export default (args: ViteConfigInput) => { + const generateScopedName = + args.mode === 'development' + ? '[name]_[local]_[hash:base64:4]' + : '[hash:base64:6]' + + return defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + test: { + globals: true, + environment: 'jsdom', + setupFiles: '__test__/setup', + }, + css: { + modules: { + localsConvention: 'camelCase', + generateScopedName, + }, }, - }, - test: { - globals: true, - environment: 'jsdom', - setupFiles: '__test__/setup', - }, -}) + }) +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..102b388 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "e-commerce", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "husky": "^8.0.0" + } + }, + "node_modules/husky": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..27457ae --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "devDependencies": { + "husky": "^8.0.0" + }, + "scripts": { + "prepare": "husky install", + "format-server": "cd server && npm run format", + "format-client": "cd client && npm run format", + "check-lint": "cd client && npm run check-lint", + "check-types": "cd client && npm run check-types" + } +} diff --git a/server/package.json b/server/package.json index a6c3f17..54a296d 100644 --- a/server/package.json +++ b/server/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "dev": "nodemon src/index.js", - "start": "node index.js" + "start": "node index.js", + "format": "prettier --write ./src" }, "dependencies": { "cookie-parser": "^1.4.6", From 3da56ee9858e95016880f87a87bb2198961144a9 Mon Sep 17 00:00:00 2001 From: builtbysuraj Date: Sun, 21 Jan 2024 17:58:16 +0530 Subject: [PATCH 03/19] chore: Formatted code --- client/src/conf/index.ts | 1 - client/src/constants/filterConstants.ts | 1 - client/src/hooks/useGetParams.ts | 2 +- client/src/layouts/filters/SidebarFilters.tsx | 2 -- .../layouts/filters/components/PriceSliderFilter.tsx | 2 +- client/src/state/slices/cartSlice.ts | 2 +- client/src/state/slices/filtersSlice.ts | 12 +++++++++--- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/client/src/conf/index.ts b/client/src/conf/index.ts index f8d9a49..4fa1ecc 100644 --- a/client/src/conf/index.ts +++ b/client/src/conf/index.ts @@ -1,4 +1,3 @@ export const ENV = { API_BASE_URL: String(import.meta.env.VITE_API_BASE_URL), - } diff --git a/client/src/constants/filterConstants.ts b/client/src/constants/filterConstants.ts index 1365d75..3416ed3 100644 --- a/client/src/constants/filterConstants.ts +++ b/client/src/constants/filterConstants.ts @@ -10,4 +10,3 @@ export const RATING_TYPE = { TWO_AND_UP: '2', ONE_AND_UP: '1', } - diff --git a/client/src/hooks/useGetParams.ts b/client/src/hooks/useGetParams.ts index 66ba33e..209d676 100644 --- a/client/src/hooks/useGetParams.ts +++ b/client/src/hooks/useGetParams.ts @@ -2,7 +2,7 @@ import { useSearchParams } from 'react-router-dom' export default function useGetParams() { const searchParams = useSearchParams()[0] - + const q = searchParams.get('q') || '' const category = searchParams.get('category') || '' const rating = searchParams.get('rating') || '' diff --git a/client/src/layouts/filters/SidebarFilters.tsx b/client/src/layouts/filters/SidebarFilters.tsx index 352a275..85d7964 100644 --- a/client/src/layouts/filters/SidebarFilters.tsx +++ b/client/src/layouts/filters/SidebarFilters.tsx @@ -22,7 +22,6 @@ export default function SidebarFilters() { Filters - {/* Clear Filters */}
@@ -33,7 +32,6 @@ export default function SidebarFilters() { handlePriceRange={handlePriceRange} /> - {/* Sort products */} Date: Sun, 21 Jan 2024 20:29:43 +0530 Subject: [PATCH 04/19] chore: Add format check pre-commit msg --- .husky/pre-commit | 4 ++-- client/package.json | 3 ++- package.json | 4 ++-- server/package.json | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index bc92427..a7c6443 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -28,7 +28,7 @@ npm run check-lint || # Check Prettier standards -npm run format-client || +npm run check-format-client || ( echo ' Prettier Check Failed. ❌ @@ -36,7 +36,7 @@ npm run format-client || false; ) -npm run format-server || +npm run check-format-server || ( echo ' Prettier Check Failed. ❌ diff --git a/client/package.json b/client/package.json index cfd6caa..4e4e3e5 100644 --- a/client/package.json +++ b/client/package.json @@ -11,7 +11,8 @@ "check-lint": "eslint . --ext ts,tsx", "check-types": "tsc --pretty --noEmit", "test": "vitest", - "format": "prettier --write ./src" + "format": "prettier --write ./src", + "check-format": "prettier --check ./src" }, "dependencies": { "@emotion/react": "^11.11.3", diff --git a/package.json b/package.json index 27457ae..a69990e 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ }, "scripts": { "prepare": "husky install", - "format-server": "cd server && npm run format", - "format-client": "cd client && npm run format", + "check-format-client": "cd client && npm run check-format", + "check-format-server": "cd server && npm run check-format", "check-lint": "cd client && npm run check-lint", "check-types": "cd client && npm run check-types" } diff --git a/server/package.json b/server/package.json index 54a296d..b5a09e1 100644 --- a/server/package.json +++ b/server/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "nodemon src/index.js", "start": "node index.js", - "format": "prettier --write ./src" + "format": "prettier --write ./src", + "check-format": "prettier --check ./src" }, "dependencies": { "cookie-parser": "^1.4.6", From 7900fb9e1d603473a37f7df22a5f42d57d3100c3 Mon Sep 17 00:00:00 2001 From: builtbysuraj Date: Mon, 22 Jan 2024 00:55:23 +0530 Subject: [PATCH 05/19] refactor: Add reusable Form and Input, Remove classNames package --- client/package-lock.json | 6 -- client/package.json | 1 - client/src/assets/css/reset.css | 4 +- .../Form.module.css} | 2 +- client/src/components/Form.tsx | 50 ++++++++++++- client/src/components/ui/Input.tsx | 45 +++++++++-- client/src/hooks/useFilters.ts | 2 +- client/src/hooks/useHandleDispatch.ts | 2 +- .../filters/components/RatingFilter.tsx | 58 +++++++------- .../filters/components/SortProductsFilter.tsx | 61 +++++++-------- client/src/layouts/footer/Footer.tsx | 17 ++--- client/src/layouts/header/Header.tsx | 19 ++--- client/src/pages/cart/CartPage.module.css | 21 ------ client/src/pages/cart/CartPage.tsx | 26 +++---- .../cart-price-details/CartPriceDetails.tsx | 7 +- .../cart/components/empty-cart/EmptyCart.tsx | 8 +- .../place-order/PlaceOrder.module.css | 21 ++++++ .../components/place-order/PlaceOrder.tsx | 6 +- client/src/pages/home/HomePage.tsx | 6 +- client/src/pages/login/LoginPage.tsx | 41 +++------- .../product-listing/ProductListingPage.tsx | 2 +- client/src/pages/signup/SignUpPage.module.css | 75 ------------------- client/src/pages/signup/SignUpPage.tsx | 41 +++------- client/src/state/services/productApi.ts | 2 +- client/src/state/slices/cartSlice.ts | 6 +- client/src/state/slices/filtersSlice.ts | 2 +- client/src/state/store.ts | 3 +- client/src/types/index.ts | 12 +-- client/src/utils/index.ts | 2 +- 29 files changed, 224 insertions(+), 324 deletions(-) rename client/src/{pages/login/LoginPage.module.css => components/Form.module.css} (97%) create mode 100644 client/src/pages/cart/components/place-order/PlaceOrder.module.css delete mode 100644 client/src/pages/signup/SignUpPage.module.css diff --git a/client/package-lock.json b/client/package-lock.json index f58b969..fb0c369 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,7 +14,6 @@ "@reduxjs/toolkit": "^2.0.1", "@tanstack/react-query": "^5.17.10", "axios": "^1.6.4", - "classnames": "^2.5.1", "lodash.debounce": "^4.0.8", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -2772,11 +2771,6 @@ "node": "*" } }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" - }, "node_modules/clsx": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", diff --git a/client/package.json b/client/package.json index 4e4e3e5..fac0522 100644 --- a/client/package.json +++ b/client/package.json @@ -21,7 +21,6 @@ "@reduxjs/toolkit": "^2.0.1", "@tanstack/react-query": "^5.17.10", "axios": "^1.6.4", - "classnames": "^2.5.1", "lodash.debounce": "^4.0.8", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/client/src/assets/css/reset.css b/client/src/assets/css/reset.css index a1ad506..d9fd081 100644 --- a/client/src/assets/css/reset.css +++ b/client/src/assets/css/reset.css @@ -14,15 +14,13 @@ html { body { line-height: 1.5; min-height: 100vh; - min-width: 100vw; - /* overflow-x: hidden; */ + max-width: 100vw; } .AppContainer { background-color: #f1f3f6; z-index: -1; min-height: 100vh; - min-width: 100rem; } input, diff --git a/client/src/pages/login/LoginPage.module.css b/client/src/components/Form.module.css similarity index 97% rename from client/src/pages/login/LoginPage.module.css rename to client/src/components/Form.module.css index 62aef7a..07a3ef7 100644 --- a/client/src/pages/login/LoginPage.module.css +++ b/client/src/components/Form.module.css @@ -49,7 +49,7 @@ border: 0; border-bottom: 1px solid grey; } -input::placeholder { +.login-form input::placeholder { opacity: 0.7; } diff --git a/client/src/components/Form.tsx b/client/src/components/Form.tsx index 0b72510..bf49114 100644 --- a/client/src/components/Form.tsx +++ b/client/src/components/Form.tsx @@ -1,7 +1,49 @@ -export default function Form() { +import { Link } from 'react-router-dom' +import styles from './Form.module.css' + +type FormProps = { + mode: string + promptText: string + promptLink: string + promptLinkText: string +} + +export default function From({ + mode, + promptText, + promptLink, + promptLinkText, +}: FormProps) { return ( - <> -
Form
- +
+
+
+ {mode} +
+
+

Get access to your Orders,

+

Wishlist and Recommendations

+
+
+
+ + + + + By continuing, you agree to Flipkart's + Terms of Use + and + Privacy Policy. + + +

+ {promptText}{' '} + + {promptLinkText} + +

+ +
+
) } diff --git a/client/src/components/ui/Input.tsx b/client/src/components/ui/Input.tsx index 174bf95..a42dcc0 100644 --- a/client/src/components/ui/Input.tsx +++ b/client/src/components/ui/Input.tsx @@ -1,9 +1,40 @@ -/* eslint-disable react-refresh/only-export-components */ +type InputProps = { + type: string + name?: string + id?: string + value: string + onChange: React.ChangeEventHandler | undefined + checked?: boolean + label?: string + className?: string +} -// import { forwardRef } from 'react' +function Input({ + type, + name, + id, + value, + onChange, + checked, + label, + className = '', +}: InputProps) { + return ( + <> + + + + ) +} -// function Input({ label, type = 'text', className = '', ...props }, ref) { -// return <>{label && } -// } - -// export default forwardRef(Input) +export default Input diff --git a/client/src/hooks/useFilters.ts b/client/src/hooks/useFilters.ts index 2e55f2e..0933bbb 100644 --- a/client/src/hooks/useFilters.ts +++ b/client/src/hooks/useFilters.ts @@ -1,6 +1,6 @@ import { SORT_TYPE } from '@/constants/filterConstants' import { useGetAllProductsQuery } from '@/state/services/productApi' -import { ProductType } from '@/types' +import type { ProductType } from '@/types' import useGetParams from './useGetParams' export default function useFilter() { diff --git a/client/src/hooks/useHandleDispatch.ts b/client/src/hooks/useHandleDispatch.ts index f78dcfb..1bb4ce0 100644 --- a/client/src/hooks/useHandleDispatch.ts +++ b/client/src/hooks/useHandleDispatch.ts @@ -6,7 +6,7 @@ import { removeFromCart, } from '@/state/slices/cartSlice' import { useAppDispatch } from '@/state/store' -import { CartType, ProductType } from '@/types' +import type { CartType, ProductType } from '@/types' export default function useHandleDispatch() { const dispatch = useAppDispatch() diff --git a/client/src/layouts/filters/components/RatingFilter.tsx b/client/src/layouts/filters/components/RatingFilter.tsx index 563a26b..2423082 100644 --- a/client/src/layouts/filters/components/RatingFilter.tsx +++ b/client/src/layouts/filters/components/RatingFilter.tsx @@ -1,5 +1,5 @@ +import Input from '@/components/ui/Input' import { RATING_TYPE } from '@/constants/filterConstants' -import { FormControlLabel, Radio, RadioGroup, Typography } from '@mui/material' type Props = { searchParams: URLSearchParams @@ -12,38 +12,30 @@ export default function RatingFilter({ }: Props) { const rating = searchParams.get('rating') || '' + const ratingItems = [ + { value: RATING_TYPE.FOUR_AND_UP, label: '4 & Up' }, + { value: RATING_TYPE.THREE_AND_UP, label: '3 & Up' }, + { value: RATING_TYPE.TWO_AND_UP, label: '2 & Up' }, + { value: RATING_TYPE.ONE_AND_UP, label: '1 & Up' }, + ] + return ( - <> - - Customer Review - - handleFilterRating(e.target.value)} - > - } - label="4 & Up" - /> - } - label="3 & Up" - /> - } - label="2 & Up" - /> - } - label="1 & Up" - /> - - +
+

Customer Review

+ {ratingItems.map((item) => ( +
+ handleFilterRating(event.target.value)} + name="sort" + label={item.label} + value={item.value} + /> +
+ ))} +
+
) } diff --git a/client/src/layouts/filters/components/SortProductsFilter.tsx b/client/src/layouts/filters/components/SortProductsFilter.tsx index 54c2412..f60871e 100644 --- a/client/src/layouts/filters/components/SortProductsFilter.tsx +++ b/client/src/layouts/filters/components/SortProductsFilter.tsx @@ -1,5 +1,5 @@ +import Input from '@/components/ui/Input' import { SORT_TYPE } from '@/constants/filterConstants' -import { Typography } from '@mui/material' type Props = { searchParams: URLSearchParams @@ -11,42 +11,33 @@ export default function SortProductsFilter({ handleSort, }: Props) { const sort = searchParams.get('sort') || '' + + const sortItems = [ + { value: SORT_TYPE.PRICE_HIGH_TO_LOW, label: 'Price high to low' }, + { value: SORT_TYPE.PRICE_LOW_TO_HIGH, label: 'Price low to high' }, + { value: SORT_TYPE.RATING_HIGH_TO_LOW, label: 'Rating High To Low' }, + ] + return ( - <> - - Sort - +
+

Sort

- handleSort(e.target.value)} - checked={sort === SORT_TYPE.PRICE_HIGH_TO_LOW} - /> - -
- handleSort(e.target.value)} - checked={sort === SORT_TYPE.PRICE_LOW_TO_HIGH} - /> - -
- handleSort(e.target.value)} - checked={sort === SORT_TYPE.RATING_HIGH_TO_LOW} - /> -
+ {sortItems.map((item) => ( +
+ handleSort(event.target.value)} + name="sort" + label={item.label} + value={item.value} + /> +
+
+ ))} +
- +
) } diff --git a/client/src/layouts/footer/Footer.tsx b/client/src/layouts/footer/Footer.tsx index a305d14..976411e 100644 --- a/client/src/layouts/footer/Footer.tsx +++ b/client/src/layouts/footer/Footer.tsx @@ -1,15 +1,12 @@ -import { Divider } from '@mui/material' -import classNames from 'classnames/bind' -import { Link } from 'react-router-dom' import gift from '@/assets/img/gift.svg' import paymentMethods from '@/assets/img/payment-method.svg' import question from '@/assets/img/question.svg' import seller from '@/assets/img/seller.svg' import star from '@/assets/img/star.svg' +import { Divider } from '@mui/material' +import { Link } from 'react-router-dom' import styles from './Footer.module.css' -const cx = classNames.bind(styles) - export default function Footer() { const about = [ 'Contact Us', @@ -41,9 +38,9 @@ export default function Footer() { ] const social = ['Facebook', 'Twitter', 'YouTube'] return ( -