diff --git a/client/index.html b/client/index.html index 90aae2f..d8f48db 100644 --- a/client/index.html +++ b/client/index.html @@ -2,7 +2,7 @@ - + Online Shopping Site for Mobiles, Electronics, Furniture, Grocery, diff --git a/client/package-lock.json b/client/package-lock.json index f58b969..0ebc8d8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,9 +12,7 @@ "@emotion/styled": "^11.11.0", "@mui/material": "^5.15.2", "@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", @@ -1641,30 +1639,6 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "node_modules/@tanstack/query-core": { - "version": "5.17.10", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.17.10.tgz", - "integrity": "sha512-bJ2oQUDBftvHcEkLS3gyzzShSeZpJyzNNRu8oHK13iNdsofyaDXtNO/c1Zy/PZYVX+PhqOXwoT42gMiEMRSSfQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.17.10", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.17.10.tgz", - "integrity": "sha512-TNmJN7LkSLzmv01Jen3JbcvhXZyRhc/ETJNjssmmlyMB8IoNvicfgvDRX2gX3q1FTONq+mfsmWintwI+ejmEUw==", - "dependencies": { - "@tanstack/query-core": "5.17.10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18.0.0" - } - }, "node_modules/@testing-library/dom": { "version": "9.3.3", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", @@ -2772,11 +2746,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..9971061 100644 --- a/client/package.json +++ b/client/package.json @@ -19,9 +19,7 @@ "@emotion/styled": "^11.11.0", "@mui/material": "^5.15.2", "@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/App.tsx b/client/src/App.tsx index fa2d263..fbd1c08 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -4,12 +4,12 @@ import { Footer, Header } from './layouts' export default function App() { return ( - <div className="AppContainer"> + <main className="AppContainer"> <Header /> <Suspense fallback="Loading..."> <Outlet /> </Suspense> <Footer /> - </div> + </main> ) } diff --git a/client/src/assets/css/global.css b/client/src/assets/css/global.css new file mode 100644 index 0000000..c1f97b9 --- /dev/null +++ b/client/src/assets/css/global.css @@ -0,0 +1,22 @@ +.AppContainer { + background-color: #f1f3f6; + z-index: -1; + min-height: 100vh; +} + +hr { + border-top: 1px solid rgba(96, 96, 96, 0.217); + margin: 15px 0 0 0; +} + +.sidebar section { + margin: 10px 0; +} + +.sidebar section input { + margin: 8px 0; +} + +.sidebar section label { + padding: 0 10px; +} diff --git a/client/src/assets/css/reset.css b/client/src/assets/css/reset.css index a1ad506..d6edfe7 100644 --- a/client/src/assets/css/reset.css +++ b/client/src/assets/css/reset.css @@ -11,18 +11,15 @@ html { font-family: system-ui, sans-serif; } +html, body { - line-height: 1.5; - min-height: 100vh; - min-width: 100vw; - /* overflow-x: hidden; */ + height: 100%; } -.AppContainer { - background-color: #f1f3f6; - z-index: -1; +body { + line-height: 1.5; min-height: 100vh; - min-width: 100rem; + max-width: 100vw; } input, @@ -32,7 +29,23 @@ select { font: inherit; } -/* responsive images/videos */ +input[type='radio'], +label[for] { + cursor: pointer; +} + +input[type='text'], +input[type='number'], +select, +textarea { + font-size: 16px; +} + +button:disabled { + opacity: 0.5; + pointer-events: none; +} + img, picture, svg, diff --git a/client/src/components/Form.tsx b/client/src/components/Form.tsx deleted file mode 100644 index 0b72510..0000000 --- a/client/src/components/Form.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function Form() { - return ( - <> - <div>Form</div> - </> - ) -} diff --git a/client/src/pages/signup/SignUpPage.module.css b/client/src/components/form/Form.module.css similarity index 97% rename from client/src/pages/signup/SignUpPage.module.css rename to client/src/components/form/Form.module.css index 62aef7a..07a3ef7 100644 --- a/client/src/pages/signup/SignUpPage.module.css +++ b/client/src/components/form/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/Form.tsx b/client/src/components/form/Form.tsx new file mode 100644 index 0000000..bf49114 --- /dev/null +++ b/client/src/components/form/Form.tsx @@ -0,0 +1,49 @@ +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 ( + <div className={styles.loginContainer}> + <div className={styles.left}> + <div className={styles.loginTitle}> + <span>{mode}</span> + </div> + <div className={styles.loginDesc}> + <p>Get access to your Orders,</p> + <p>Wishlist and Recommendations</p> + </div> + </div> + <div className={styles.right}> + <form className={styles.loginForm}> + <input type="text" placeholder="Enter Email/Username" /> + <input type="password" placeholder="Password" /> + <small> + By continuing, you agree to Flipkart's + <span className={styles.demoLink}> Terms of Use </span> + and + <span className={styles.demoLink}> Privacy Policy.</span> + </small> + <button>{mode}</button> + <p> + {promptText}{' '} + <Link to={promptLink} className={styles.demoLink}> + {promptLinkText} + </Link> + </p> + </form> + </div> + </div> + ) +} diff --git a/client/src/components/loader/Loader.tsx b/client/src/components/loader/Loader.tsx new file mode 100644 index 0000000..0a79188 --- /dev/null +++ b/client/src/components/loader/Loader.tsx @@ -0,0 +1,17 @@ +import { CircularProgress } from '@mui/material' + +export default function Loader() { + return ( + <div + style={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + minHeight: '100vh', + maxWidth: '100vw', + }} + > + <CircularProgress size="3rem" /> + </div> + ) +} diff --git a/client/src/components/ui/Button.tsx b/client/src/components/ui/Button.tsx deleted file mode 100644 index c72c12d..0000000 --- a/client/src/components/ui/Button.tsx +++ /dev/null @@ -1,5 +0,0 @@ -type ButtonType = React.ComponentPropsWithoutRef<'button'> - -export default function Button({ children, ...props }: ButtonType) { - return <button {...props}>{children}</button> -} diff --git a/client/src/components/ui/Input.tsx b/client/src/components/ui/Input.tsx index 174bf95..af080ca 100644 --- a/client/src/components/ui/Input.tsx +++ b/client/src/components/ui/Input.tsx @@ -1,9 +1,43 @@ -/* eslint-disable react-refresh/only-export-components */ +type InputProps = { + type: string + name?: string + placeholder?: string + id?: string + value: string + onChange: React.ChangeEventHandler<HTMLInputElement> | undefined + checked?: boolean + label?: string + className?: string +} -// import { forwardRef } from 'react' +function Input({ + type, + name, + placeholder, + id, + value, + onChange, + checked, + label, + className = '', +}: InputProps) { + return ( + <> + <input + type={type} + name={name} + placeholder={placeholder} + id={id} + value={value} + onChange={onChange} + checked={checked} + className={className} + /> + <label className={className} htmlFor={id}> + {label} + </label> + </> + ) +} -// function Input({ label, type = 'text', className = '', ...props }, ref) { -// return <>{label && <label htmlFor="">{label}</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/SidebarFilters.tsx b/client/src/layouts/filters/SidebarFilters.tsx index 85d7964..1f30f3a 100644 --- a/client/src/layouts/filters/SidebarFilters.tsx +++ b/client/src/layouts/filters/SidebarFilters.tsx @@ -1,53 +1,44 @@ -import { Box, FormControl, Typography } from '@mui/material' -import { useSearchParams } from 'react-router-dom' +import { memo } from 'react' -import useHandleDispatch from '@/hooks/useHandleDispatch' +import AppliedFilters from './components/applied-filters/AppliedFilters' import CategoryFilter from './components/CategoryFilter' +import ClearFilter from './components/clear-filter/ClearFilter' import PriceSliderFilter from './components/PriceSliderFilter' import RatingFilter from './components/RatingFilter' import SortProductsFilter from './components/SortProductsFilter' -export default function SidebarFilters() { - const searchParams = useSearchParams()[0] - const { - handleFilterRating, - handleSort, - handlePriceRange, - handleClearFilter, - } = useHandleDispatch() - +function SidebarFilters() { return ( - <> - <Typography component="h1" variant="h5" fontWeight="bold"> - Filters - </Typography> - <Box display="flex" justifyContent="center" flexDirection="column"> - {/* Clear Filters */} - <button onClick={handleClearFilter}>Clear Filters</button> - <br /> - - {/* Price Filter */} - <PriceSliderFilter - searchParams={searchParams} - handlePriceRange={handlePriceRange} - /> - <FormControl> - {/* Sort products */} - <SortProductsFilter - searchParams={searchParams} - handleSort={handleSort} - /> - - {/* Ratings filter */} - <RatingFilter - searchParams={searchParams} - handleFilterRating={handleFilterRating} - /> - </FormControl> - - {/* Category Filters */} - <CategoryFilter searchParams={searchParams} /> - </Box> - </> + <div + style={{ + background: 'white', + padding: '0.8rem', + }} + className="sidebar" + > + <h3>Filters</h3> + <div + style={{ + display: 'flex', + justifyContent: 'center', + flexDirection: 'column', + }} + > + <ClearFilter /> + + <AppliedFilters /> + + <PriceSliderFilter /> + + <SortProductsFilter /> + + <RatingFilter /> + + <CategoryFilter /> + </div> + </div> ) } + +const MemoizedSidebarFilters = memo(SidebarFilters) +export default MemoizedSidebarFilters diff --git a/client/src/layouts/filters/components/CategoryFilter.tsx b/client/src/layouts/filters/components/CategoryFilter.tsx index d976beb..797563a 100644 --- a/client/src/layouts/filters/components/CategoryFilter.tsx +++ b/client/src/layouts/filters/components/CategoryFilter.tsx @@ -1,7 +1,12 @@ +import { memo } from 'react' + +import Input from '@/components/ui/Input' +import useGetParams from '@/hooks/useGetParams' import useHandleDispatch from '@/hooks/useHandleDispatch' -import { FormControlLabel, Radio, RadioGroup, Typography } from '@mui/material' -const data = { +type CategoryType = Record<string, string> + +const data: CategoryType = { Smartphones: 'smartphones', Laptops: 'laptops', Fragrances: 'fragrances', @@ -24,47 +29,27 @@ const data = { 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) +function CategoryFilter() { const { handleCategoryFilter } = useHandleDispatch() - const category = searchParams.get('category') || '' - + const { category: categoryParam } = useGetParams() return ( - <> - <Typography component="label" fontWeight="bold" id="category"> - Categories - </Typography> - <RadioGroup - aria-labelledby="category" - name="categories" - value={category} - onChange={(e) => handleCategoryFilter(e.target.value)} - > - {Object.keys(data).map((category, index) => ( - <FormControlLabel - // @ts-expect-error - type 'string' can't be used to index type + <section> + <h4>Categories</h4> + {Object.keys(data).map((category) => ( + <div key={category}> + <Input + type="radio" value={data[category]} - key={index} - control={<Radio />} label={category} + id={category} + onChange={(e) => handleCategoryFilter(e.target.value)} + checked={categoryParam === data[category]} /> - ))} - </RadioGroup> - </> + </div> + ))} + </section> ) } + +const MemoizedCategoryFilter = memo(CategoryFilter) +export default MemoizedCategoryFilter diff --git a/client/src/layouts/filters/components/PriceSliderFilter.tsx b/client/src/layouts/filters/components/PriceSliderFilter.tsx index cfd9602..2f74e98 100644 --- a/client/src/layouts/filters/components/PriceSliderFilter.tsx +++ b/client/src/layouts/filters/components/PriceSliderFilter.tsx @@ -1,24 +1,17 @@ -import { Box, Slider, Typography } from '@mui/material' +import Slider from '@mui/material/Slider' +import { memo } from 'react' -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] +import useGetParams from '@/hooks/useGetParams' +import useHandleDispatch from '@/hooks/useHandleDispatch' +function PriceSliderFilter() { + const { handlePriceRange } = useHandleDispatch() + const { price } = useGetParams() return ( - <> - <Typography fontWeight="bold">Price</Typography> - <Typography fontWeight="bold"> - <br /> - {`$${price[0]} - $${price[1]}`} - </Typography> - <Box sx={{ m: 2 }}> + <section> + <h4>Price</h4> + <h4>{`$${price[0]} - $${price[1]}`}</h4> + <div> <Slider value={price} onChange={handlePriceRange} @@ -26,8 +19,12 @@ export default function PriceSliderFilter({ max={2000} min={10} step={100} + size="small" /> - </Box> - </> + </div> + </section> ) } + +const MemoizedPriceSliderFilter = memo(PriceSliderFilter) +export default MemoizedPriceSliderFilter diff --git a/client/src/layouts/filters/components/RatingFilter.tsx b/client/src/layouts/filters/components/RatingFilter.tsx index 563a26b..09473c7 100644 --- a/client/src/layouts/filters/components/RatingFilter.tsx +++ b/client/src/layouts/filters/components/RatingFilter.tsx @@ -1,49 +1,40 @@ -import { RATING_TYPE } from '@/constants/filterConstants' -import { FormControlLabel, Radio, RadioGroup, Typography } from '@mui/material' +import { memo } from 'react' -type Props = { - searchParams: URLSearchParams - handleFilterRating: (arg0: string) => void -} +import Input from '@/components/ui/Input' +import { RATING_TYPE } from '@/constants/filterConstants' +import useGetParams from '@/hooks/useGetParams' +import useHandleDispatch from '@/hooks/useHandleDispatch' -export default function RatingFilter({ - searchParams, - handleFilterRating, -}: Props) { - const rating = searchParams.get('rating') || '' +const ratingItems = [ + { value: RATING_TYPE.FOUR_AND_UP, label: '4★ & above' }, + { value: RATING_TYPE.THREE_AND_UP, label: '3★ & above' }, + { value: RATING_TYPE.TWO_AND_UP, label: '2★ & above' }, + { value: RATING_TYPE.ONE_AND_UP, label: '1★ & above' }, +] +function RatingFilter() { + const { rating } = useGetParams() + const { handleFilterRating } = useHandleDispatch() return ( - <> - <Typography component="label" fontWeight="bold" id="rating"> - Customer Review - </Typography> - <RadioGroup - aria-labelledby="rating" - name="rating filters" - value={rating} - onChange={(e) => handleFilterRating(e.target.value)} - > - <FormControlLabel - value={RATING_TYPE.FOUR_AND_UP} - control={<Radio />} - label="4 & Up" - /> - <FormControlLabel - value={RATING_TYPE.THREE_AND_UP} - control={<Radio />} - label="3 & Up" - /> - <FormControlLabel - value={RATING_TYPE.TWO_AND_UP} - control={<Radio />} - label="2 & Up" - /> - <FormControlLabel - value={RATING_TYPE.ONE_AND_UP} - control={<Radio />} - label="1 & Up" - /> - </RadioGroup> - </> + <section> + <h4>Customer Review</h4> + {ratingItems.map((item) => ( + <div key={item.value}> + <Input + type="radio" + id={item.label} + checked={rating === item.value} + onChange={(event) => handleFilterRating(event.target.value)} + name="rating" + label={item.label} + value={item.value} + /> + </div> + ))} + <hr /> + </section> ) } + +const MemoizedRatingFilter = memo(RatingFilter) +export default MemoizedRatingFilter diff --git a/client/src/layouts/filters/components/SortProductsFilter.tsx b/client/src/layouts/filters/components/SortProductsFilter.tsx index 54c2412..01c1f95 100644 --- a/client/src/layouts/filters/components/SortProductsFilter.tsx +++ b/client/src/layouts/filters/components/SortProductsFilter.tsx @@ -1,52 +1,42 @@ +import { memo } from 'react' + +import Input from '@/components/ui/Input' import { SORT_TYPE } from '@/constants/filterConstants' -import { Typography } from '@mui/material' +import useGetParams from '@/hooks/useGetParams' +import useHandleDispatch from '@/hooks/useHandleDispatch' -type Props = { - searchParams: URLSearchParams - handleSort: (arg0: string) => void -} +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' }, +] -export default function SortProductsFilter({ - searchParams, - handleSort, -}: Props) { - const sort = searchParams.get('sort') || '' +function SortProductsFilter() { + const { sort } = useGetParams() + const { handleSort } = useHandleDispatch() return ( - <> - <Typography component="label" fontWeight="bold" id="sort"> - Sort - </Typography> - <form> - <input - type="radio" - name="sort" - id="Sort by high to low price" - value={SORT_TYPE.PRICE_HIGH_TO_LOW} - onChange={(e) => handleSort(e.target.value)} - checked={sort === SORT_TYPE.PRICE_HIGH_TO_LOW} - /> - <label htmlFor="Sort by high to low price">Price high to low</label> - <br /> - <input - type="radio" - name="sort" - id="Sort by low to high price" - value={SORT_TYPE.PRICE_LOW_TO_HIGH} - onChange={(e) => handleSort(e.target.value)} - checked={sort === SORT_TYPE.PRICE_LOW_TO_HIGH} - /> - <label htmlFor="Sort by low to high price">Price low to high</label> - <br /> - <input - type="radio" - name="sort" - id="Rating High To Low" - value={SORT_TYPE.RATING_HIGH_TO_LOW} - onChange={(e) => handleSort(e.target.value)} - checked={sort === SORT_TYPE.RATING_HIGH_TO_LOW} - /> - <label htmlFor="Rating High To Low">Rating High To Low</label> <br /> - </form> - </> + <section> + <h4>Sort</h4> + <div> + {sortItems.map((item) => ( + <div key={item.value}> + <Input + type="radio" + id={item.label} + checked={sort === item.value} + onChange={(event) => handleSort(event.target.value)} + name="sort" + label={item.label} + value={item.value} + /> + <br /> + </div> + ))} + <hr /> + </div> + </section> ) } + +const MemoizedSortProductsFilter = memo(SortProductsFilter) +export default MemoizedSortProductsFilter diff --git a/client/src/layouts/filters/components/applied-filters/AppliedFilters.module.css b/client/src/layouts/filters/components/applied-filters/AppliedFilters.module.css new file mode 100644 index 0000000..d874a86 --- /dev/null +++ b/client/src/layouts/filters/components/applied-filters/AppliedFilters.module.css @@ -0,0 +1,22 @@ +.filter-list { + display: flex; + align-items: center; + flex-wrap: wrap; +} + +.filter-list .filter-item { + margin: 2px 4px; + padding: 0.4rem; + background-color: #e0e0e0; + font-size: 14px; + cursor: pointer; +} + +.filter-item:hover { + text-decoration: line-through; +} + +.no-filter { + font-style: italic; + color: grey; +} diff --git a/client/src/layouts/filters/components/applied-filters/AppliedFilters.tsx b/client/src/layouts/filters/components/applied-filters/AppliedFilters.tsx new file mode 100644 index 0000000..fef759d --- /dev/null +++ b/client/src/layouts/filters/components/applied-filters/AppliedFilters.tsx @@ -0,0 +1,40 @@ +import { memo } from 'react' +import { useSearchParams } from 'react-router-dom' +import styles from './AppliedFilters.module.css' + +function AppliedFilters() { + const [searchParams, setSearchParams] = useSearchParams() + const params = [] + + for (const entry of searchParams.entries()) { + params.push(entry) + } + + const handleRemoveFilter = (key: string) => { + searchParams.delete(key) + setSearchParams(searchParams) + } + + return ( + <section> + <ul className={styles.filterList}> + {params.length > 0 ? ( + params.map(([key, value]) => ( + <li + className={styles.filterItem} + key={key} + onClick={() => handleRemoveFilter(key)} + > + ✕ {value} + </li> + )) + ) : ( + <li className={styles.noFilter}>No filters applied</li> + )} + </ul> + </section> + ) +} + +const MemoizedAppliedFilters = memo(AppliedFilters) +export default MemoizedAppliedFilters diff --git a/client/src/layouts/filters/components/clear-filter/ClearFilter.module.css b/client/src/layouts/filters/components/clear-filter/ClearFilter.module.css new file mode 100644 index 0000000..f2a71a1 --- /dev/null +++ b/client/src/layouts/filters/components/clear-filter/ClearFilter.module.css @@ -0,0 +1,6 @@ +.clear-btn { + all: unset; + cursor: pointer; + color: #2874f0; + font-size: 14px; +} diff --git a/client/src/layouts/filters/components/clear-filter/ClearFilter.tsx b/client/src/layouts/filters/components/clear-filter/ClearFilter.tsx new file mode 100644 index 0000000..3787277 --- /dev/null +++ b/client/src/layouts/filters/components/clear-filter/ClearFilter.tsx @@ -0,0 +1,13 @@ +import useHandleDispatch from '@/hooks/useHandleDispatch' +import styles from './ClearFilter.module.css' + +export default function ClearFilter() { + const { handleClearFilter } = useHandleDispatch() + return ( + <section> + <button className={styles.clearBtn} onClick={handleClearFilter}> + CLEAR ALL + </button> + </section> + ) +} diff --git a/client/src/layouts/footer/Footer.tsx b/client/src/layouts/footer/Footer.tsx index a305d14..aad4c84 100644 --- a/client/src/layouts/footer/Footer.tsx +++ b/client/src/layouts/footer/Footer.tsx @@ -1,6 +1,7 @@ import { Divider } from '@mui/material' -import classNames from 'classnames/bind' +import { memo } from 'react' 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' @@ -8,9 +9,7 @@ import seller from '@/assets/img/seller.svg' import star from '@/assets/img/star.svg' import styles from './Footer.module.css' -const cx = classNames.bind(styles) - -export default function Footer() { +function Footer() { const about = [ 'Contact Us', 'About Us', @@ -41,9 +40,9 @@ export default function Footer() { ] const social = ['Facebook', 'Twitter', 'YouTube'] return ( - <footer className={cx('footer-container')}> - <div className={cx('top')}> - <div className={cx('left')}> + <footer className={styles.footerContainer}> + <div className={styles.top}> + <div className={styles.left}> <div> <ul> <p>ABOUT</p> @@ -89,7 +88,7 @@ export default function Footer() { </ul> </div> </div> - <div className={cx('right')}> + <div className={styles.right}> <div> <p>Mail Us:</p> <br /> @@ -121,7 +120,7 @@ export default function Footer() { </div> </div> <Divider sx={{ bgcolor: 'white', p: 0 }} /> - <div className={cx('bottom')}> + <div className={styles.bottom}> <div> <img src={seller} alt="Become a Seller" /> <Link to="#">Become a Seller</Link> @@ -148,3 +147,6 @@ export default function Footer() { </footer> ) } + +const MemoizedFooter = memo(Footer) +export default MemoizedFooter diff --git a/client/src/layouts/header/Header.tsx b/client/src/layouts/header/Header.tsx index d951673..aaf6afc 100644 --- a/client/src/layouts/header/Header.tsx +++ b/client/src/layouts/header/Header.tsx @@ -1,45 +1,44 @@ -import { Link, useSearchParams } from 'react-router-dom' +import { memo } from 'react' +import { Link } from 'react-router-dom' import cart from '@/assets/img/cart.svg' import search from '@/assets/img/search.svg' +import Input from '@/components/ui/Input' +import useGetParams from '@/hooks/useGetParams' import useHandleDispatch from '@/hooks/useHandleDispatch' import { useAppSelector } from '@/state/store' -import classNames from 'classnames/bind' import styles from './Header.module.css' import flipkart from '/flipkart.png' -const cx = classNames.bind(styles) - -export default function Header() { +function Header() { const cartData = useAppSelector((state) => state.cart) - const searchParams = useSearchParams()[0] const { handleSearchQuery } = useHandleDispatch() - const q = searchParams.get('q') || '' - + const { q } = useGetParams() return ( - <header className={cx('header-container')}> - <div className={cx('header-wrapper')}> + <header className={styles.headerContainer}> + <div className={styles.headerWrapper}> <div> <Link to="/"> <img width="75" src={flipkart} alt="Flipkart" title="Flipkart" /> </Link> </div> - <div className={cx('input-wrapper')}> - <input - className={cx('header-input')} + <div className={styles.inputWrapper}> + <Input + type="text" + className={styles.headerInput} placeholder="Search for products, brands and more" value={q} onChange={(e) => handleSearchQuery(e.target.value)} /> <img - className={cx('header-search-icon')} + className={styles.headerSearchIcon} src={search} alt="Search Icon" /> </div> <div> <Link to="/login"> - <button className={cx('header-login-btn')}>Login</button> + <button className={styles.headerLoginBtn}>Login</button> </Link> </div> <div> @@ -52,9 +51,9 @@ export default function Header() { <Link to="#">More</Link> </div> <div> - <Link className={cx('cart-wrapper')} to="/cart"> + <Link className={styles.cartWrapper} to="/cart"> <img width={18} src={cart} alt="Cart Icon" /> - <div className={cx('cart-item')}>{cartData.length}</div> + <div className={styles.cartItem}>{cartData.length}</div> <span>cart</span> </Link> </div> @@ -62,3 +61,6 @@ export default function Header() { </header> ) } + +const MemoizedHeader = memo(Header) +export default MemoizedHeader diff --git a/client/src/main.tsx b/client/src/main.tsx index 70dbe5b..7c8e4b1 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,10 +1,15 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { createRoot } from 'react-dom/client' import { Provider } from 'react-redux' import { RouterProvider } from 'react-router-dom' +// Styles +import './assets/css/global.css' import './assets/css/reset.css' + +// Routes import router from './routes' + +// Store import store from './state/store' if (process.env.NODE_ENV === 'production') { @@ -13,12 +18,8 @@ if (process.env.NODE_ENV === 'production') { console.error = () => {} } -const queryClient = new QueryClient() - createRoot(document.querySelector('#root') as HTMLElement).render( <Provider store={store}> - <QueryClientProvider client={queryClient}> - <RouterProvider router={router} /> - </QueryClientProvider> + <RouterProvider router={router} /> </Provider> ) diff --git a/client/src/pages/cart/CartPage.module.css b/client/src/pages/cart/CartPage.module.css index 04261cf..ff02f38 100644 --- a/client/src/pages/cart/CartPage.module.css +++ b/client/src/pages/cart/CartPage.module.css @@ -50,7 +50,6 @@ background-color: var(--color-white-bg); margin-bottom: 0.1rem; padding: 1rem; - position: relative; } .cart-item-quantity { @@ -67,23 +66,3 @@ .item-margin-top { margin-top: 1.1rem; } - -.place-order { - height: 6rem; -} - -.place-order button { - position: absolute; - padding: 1rem; - border: 0; - outline: 0; - background-color: var(--color-brand-orange); - right: 30px; - bottom: 20px; - cursor: pointer; - border-radius: 3px; - width: 13rem; - color: white; - font-size: 1rem; - font-weight: 500; -} diff --git a/client/src/pages/cart/CartPage.tsx b/client/src/pages/cart/CartPage.tsx index d62ddc6..15b2d65 100644 --- a/client/src/pages/cart/CartPage.tsx +++ b/client/src/pages/cart/CartPage.tsx @@ -1,15 +1,11 @@ -import classNames from 'classnames/bind' - import useHandleDispatch from '@/hooks/useHandleDispatch' import { useAppSelector } from '@/state/store' -import { CartType } from '@/types' +import type { CartType } from '@/types' import styles from './CartPage.module.css' import CartPriceDetails from './components/cart-price-details/CartPriceDetails' import EmptyCart from './components/empty-cart/EmptyCart' import PlaceOrder from './components/place-order/PlaceOrder' -const cx = classNames.bind(styles) - export default function CartPage() { const cartData = useAppSelector((state) => state.cart) const { handleRemoveFromCart, handleDecrementCartItem, handleAddToCart } = @@ -20,15 +16,15 @@ export default function CartPage() { } return ( - <div className={cx('cart-container')}> + <div className={styles.cartContainer}> <div> {cartData?.map((product: CartType) => ( - <div className={cx('cart-item')} key={product.cartItemId}> - <div className={cx('cart-item-product-info')}> - <div className={cx('cart-image')}> + <div className={styles.cartItem} key={product.cartItemId}> + <div className={styles.cartItemProductInfo}> + <div className={styles.cartImage}> <img width={200} src={product.thumbnail} /> </div> - <div className={cx('item-margin-top')}> + <div className={styles.itemMarginTop}> <p>{product.title}</p> <small>{product.description}</small> <s>$871</s> <strong>${product.price} </strong> @@ -39,9 +35,9 @@ export default function CartPage() { </div> <small>Delivery by Mon Jan 15</small> </div> - <div className={cx('cart-item-quantity')}> + <div className={styles.cartItemQuantity}> <button - className={cx('cart-quantity-btn')} + className={styles.cartQuantityBtn} onClick={() => handleDecrementCartItem(product)} > - @@ -50,14 +46,14 @@ export default function CartPage() { {product.quantity} </span> <button - className={cx('cart-quantity-btn')} + className={styles.cartQuantityBtn} onClick={() => handleAddToCart(product)} > + </button> - <button className={cx('cart-item-btn')}>SAVE FOR LATER</button> + <button className={styles.cartItemBtn}>SAVE FOR LATER</button> <button - className={cx('cart-item-btn')} + className={styles.cartItemBtn} onClick={() => handleRemoveFromCart(product.cartItemId)} > REMOVE diff --git a/client/src/pages/cart/components/cart-price-details/CartPriceDetails.tsx b/client/src/pages/cart/components/cart-price-details/CartPriceDetails.tsx index 9062a83..a5a47d4 100644 --- a/client/src/pages/cart/components/cart-price-details/CartPriceDetails.tsx +++ b/client/src/pages/cart/components/cart-price-details/CartPriceDetails.tsx @@ -1,19 +1,16 @@ import { Divider } from '@mui/material' -import classNames from 'classnames/bind' -import { CartType } from '@/types' +import type { CartType } from '@/types' import { totalCartPrice } from '@/utils' import styles from './CartPriceDetails.module.css' -const cx = classNames.bind(styles) - type Props = { cartData: CartType[] } export default function CartPriceDetails({ cartData }: Props) { return ( - <div className={cx('cart-price')}> + <div className={styles.cartPrice}> <span>PRICE DETAILS</span> <Divider /> <section> diff --git a/client/src/pages/cart/components/empty-cart/EmptyCart.tsx b/client/src/pages/cart/components/empty-cart/EmptyCart.tsx index 1b1afdf..f42696d 100644 --- a/client/src/pages/cart/components/empty-cart/EmptyCart.tsx +++ b/client/src/pages/cart/components/empty-cart/EmptyCart.tsx @@ -1,17 +1,15 @@ -import classNames from 'classnames/bind' import { Link } from 'react-router-dom' + import emptyCart from '@/assets/img/empty-cart.png' import styles from './EmptyCart.module.css' -const cx = classNames.bind(styles) - export default function EmptyCart() { return ( - <div className={cx('empty-cart-container')}> + <div className={styles.emptyCartContainer}> <img width={280} src={emptyCart} alt="Empty Cart" /> <h2>No items in Cart</h2> <Link to="/"> - <button className={cx('empty-cart-btn')}>Back to Home</button> + <button className={styles.emptyCartBtn}>Back to Home</button> </Link> </div> ) diff --git a/client/src/pages/cart/components/place-order/PlaceOrder.module.css b/client/src/pages/cart/components/place-order/PlaceOrder.module.css new file mode 100644 index 0000000..e8b1941 --- /dev/null +++ b/client/src/pages/cart/components/place-order/PlaceOrder.module.css @@ -0,0 +1,21 @@ +.place-order { + text-align: end; + background-color: white; + box-shadow: 0 -2px 10px 0 rgba(0, 0, 0, 0.1); +} + +.place-order button { + margin: 1rem 1rem; + padding: 1rem; + border: 0; + outline: 0; + background-color: var(--color-brand-orange); + right: 30px; + bottom: 20px; + cursor: pointer; + border-radius: 3px; + width: 15rem; + color: white; + font-size: 1rem; + font-weight: 500; +} diff --git a/client/src/pages/cart/components/place-order/PlaceOrder.tsx b/client/src/pages/cart/components/place-order/PlaceOrder.tsx index 0ea8d3f..1347b13 100644 --- a/client/src/pages/cart/components/place-order/PlaceOrder.tsx +++ b/client/src/pages/cart/components/place-order/PlaceOrder.tsx @@ -1,6 +1,8 @@ -import { totalCartPrice } from '@/utils' import axios from 'axios' +import { totalCartPrice } from '@/utils' +import styles from './PlaceOrder.module.css' + type Props = { cartData: [] } @@ -39,7 +41,7 @@ export default function PlaceOrder({ cartData }: Props) { } return ( - <div className=""> + <div className={styles.placeOrder}> <button onClick={handleCheckout}>PLACE ORDER</button> </div> ) diff --git a/client/src/pages/home/HomePage.tsx b/client/src/pages/home/HomePage.tsx index d4a7692..848fd62 100644 --- a/client/src/pages/home/HomePage.tsx +++ b/client/src/pages/home/HomePage.tsx @@ -1,13 +1,9 @@ -import classNames from 'classnames/bind' import { Link } from 'react-router-dom' - import styles from './HomePage.module.css' -const cx = classNames.bind(styles) - export default function HomePage() { return ( - <div className={cx('home-container')}> + <div className={styles.homeContainer}> <div>HomePage</div> <Link to="/products"> Product Listing </Link> <Link to="/cart"> Cart </Link> diff --git a/client/src/pages/index.ts b/client/src/pages/index.ts deleted file mode 100644 index 0b9a69b..0000000 --- a/client/src/pages/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import CartPage from './cart/CartPage' -import HomePage from './home/HomePage' -import LoginPage from './login/LoginPage' -import ProductDetailsPage from './product-details/ProductDetailsPage' -import ProductListingPage from './product-listing/ProductListingPage' -import SignUpPage from './signup/SignUpPage' - -export { - CartPage, - HomePage, - LoginPage, - ProductDetailsPage, - ProductListingPage, - SignUpPage, -} diff --git a/client/src/pages/login/LoginPage.module.css b/client/src/pages/login/LoginPage.module.css deleted file mode 100644 index 62aef7a..0000000 --- a/client/src/pages/login/LoginPage.module.css +++ /dev/null @@ -1,75 +0,0 @@ -.login-container { - margin: 1.4rem auto; - width: 50rem; - height: 31rem; - background-color: white; - display: flex; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.08); -} - -.left { - width: 24rem; - background-color: #2874f0; - padding: 2.2rem; - color: white; - height: 100%; -} - -.login-title { - font-size: 1.7rem; - font-weight: 500; - margin: 0.5rem 0; -} - -.login-desc { - color: #dbdbdb; - font-size: 1.1rem; -} - -.right { - padding: 2rem; - width: 35rem; - height: 100%; -} - -.right .login-form { - margin-top: 2rem; - display: flex; - align-items: center; - flex-direction: column; - gap: 0.8rem; -} - -.login-form input { - width: 100%; - padding: 0.4rem 1rem; - margin: 0.6rem 0; - outline: 0; - font-size: 1.1rem; - border: 0; - border-bottom: 1px solid grey; -} -input::placeholder { - opacity: 0.7; -} - -.login-form button { - border: 0; - background-color: #fb641b; - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2); - width: 100%; - height: 3rem; - cursor: pointer; - font-size: 1.1rem; - color: white; -} - -small { - text-align: left; - font-size: 0.7rem; - color: #878787; -} - -.demo-link { - color: #2874f0; -} diff --git a/client/src/pages/login/LoginPage.tsx b/client/src/pages/login/LoginPage.tsx index 498505c..4f68958 100644 --- a/client/src/pages/login/LoginPage.tsx +++ b/client/src/pages/login/LoginPage.tsx @@ -1,37 +1,14 @@ -import { Link } from 'react-router-dom' -import styles from './LoginPage.module.css' +import Form from '@/components/form/Form' export default function LoginPage() { return ( - <div className={styles.loginContainer}> - <div className={styles.left}> - <div className={styles.loginTitle}> - <span>Login</span> - </div> - <div className={styles.loginDesc}> - <p>Get access to your Orders,</p> - <p>Wishlist and Recommendations</p> - </div> - </div> - <div className={styles.right}> - <form className={styles.loginForm}> - <input type="text" placeholder="Enter Email/Username" /> - <input type="password" placeholder="Password" /> - <small> - By continuing, you agree to Flipkart's - <span className={styles.demoLink}> Terms of Use </span> - and - <span className={styles.demoLink}> Privacy Policy.</span> - </small> - <button>Login</button> - <p> - Dont have an account?{' '} - <Link to="/signup" className={styles.demoLink}> - Signup - </Link> - </p> - </form> - </div> - </div> + <> + <Form + mode="Login" + promptLinkText="Signup" + promptLink="/signup" + promptText="Dont have an account?" + /> + </> ) } diff --git a/client/src/pages/product-listing/ProductListingPage.tsx b/client/src/pages/product-listing/ProductListingPage.tsx index d3400bf..ecd5f4e 100644 --- a/client/src/pages/product-listing/ProductListingPage.tsx +++ b/client/src/pages/product-listing/ProductListingPage.tsx @@ -1,53 +1,25 @@ -import { Link } from 'react-router-dom' +import { memo } from 'react' +import Loader from '@/components/loader/Loader' 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' +import Products from './components/products/Products' -export default function ProductListingPage() { +function ProductListingPage() { const { filteredData, isLoading } = useFilter() - if (isLoading) return <h1>Loading....</h1> - + if (isLoading) return <Loader /> return ( - <div style={{ display: 'flex', margin: '1rem', gap: 2 }}> - <div style={{ flex: 1 }}> + <div style={{ display: 'flex', margin: '0.5rem', gap: '10px' }}> + <aside style={{ flex: 1 }}> <SidebarFilters /> + </aside> + <div style={{ flex: 4.6 }}> + <Products filteredData={filteredData} /> </div> - <Box sx={{ flex: 4 }}> - {filteredData?.map((product: ProductType) => ( - <Link key={product.id} to={`/products/${product.id}`}> - <Paper - sx={{ - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - // margin: 2, - p: 2, - mb: 1, - }} - > - <div style={{ display: 'flex', alignItems: 'center' }}> - <img - loading="lazy" - width={200} - src={product.thumbnail} - alt={product.title} - /> - <h3>{product.title}</h3> - <h3>{product.rating}</h3> - {/* <p>{product.description}</p> */} - </div> - - <div> - <h2>${product.price}</h2> - </div> - </Paper> - </Link> - ))} - </Box> </div> ) } + +const MemoizedProductListingPage = memo(ProductListingPage) +export default MemoizedProductListingPage diff --git a/client/src/pages/product-listing/components/product-card/ProductCard.module.css b/client/src/pages/product-listing/components/product-card/ProductCard.module.css new file mode 100644 index 0000000..e69de29 diff --git a/client/src/pages/product-listing/components/product-card/ProductCard.tsx b/client/src/pages/product-listing/components/product-card/ProductCard.tsx new file mode 100644 index 0000000..a3b4621 --- /dev/null +++ b/client/src/pages/product-listing/components/product-card/ProductCard.tsx @@ -0,0 +1,42 @@ +import { ProductType } from '@/types' +import Paper from '@mui/material/Paper' +import { Link } from 'react-router-dom' + +type Props = { + product: ProductType +} + +export default function ProductCard({ product }: Props) { + return ( + <div> + <Link key={product.id} to={`/products/${product.id}`}> + <Paper + sx={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + p: 3, + mb: 0.1, + }} + elevation={0} + > + <div style={{ display: 'flex', alignItems: 'center' }}> + <img + loading="lazy" + width={200} + src={product.thumbnail} + alt={product.title} + /> + <h3>{product.title}</h3> + <h3>{product.rating}</h3> + <p>{product.description}</p> + </div> + + <div> + <h2>${product.price}</h2> + </div> + </Paper> + </Link> + </div> + ) +} diff --git a/client/src/pages/product-listing/components/products/Products.tsx b/client/src/pages/product-listing/components/products/Products.tsx new file mode 100644 index 0000000..55a30a6 --- /dev/null +++ b/client/src/pages/product-listing/components/products/Products.tsx @@ -0,0 +1,16 @@ +import type { ProductType } from '@/types' +import ProductCard from '../product-card/ProductCard' + +type Props = { + filteredData: ProductType[] +} + +export default function Products({ filteredData }: Props) { + return ( + <div> + {filteredData?.map((product) => ( + <ProductCard product={product} key={product.id} /> + ))} + </div> + ) +} diff --git a/client/src/pages/signup/SignUpPage.tsx b/client/src/pages/signup/SignUpPage.tsx index 308566d..c181d06 100644 --- a/client/src/pages/signup/SignUpPage.tsx +++ b/client/src/pages/signup/SignUpPage.tsx @@ -1,37 +1,14 @@ -import { Link } from 'react-router-dom' -import styles from './SignUpPage.module.css' +import Form from '@/components/form/Form' export default function SignUpPage() { return ( - <div className={styles.loginContainer}> - <div className={styles.left}> - <div className={styles.loginTitle}> - <span>Login</span> - </div> - <div className={styles.loginDesc}> - <p>Get access to your Orders,</p> - <p>Wishlist and Recommendations</p> - </div> - </div> - <div className={styles.right}> - <form className={styles.loginForm}> - <input type="text" placeholder="Enter Email/Username" /> - <input type="password" placeholder="Password" /> - <small> - By continuing, you agree to Flipkart's - <span className={styles.demoLink}> Terms of Use </span> - and - <span className={styles.demoLink}> Privacy Policy.</span> - </small> - <button>Login</button> - <p> - Already have an account?{' '} - <Link to="/login" className={styles.demoLink}> - Login - </Link> - </p> - </form> - </div> - </div> + <> + <Form + mode="Signup" + promptText="Already have an account?" + promptLink="/login" + promptLinkText="Login" + /> + </> ) } diff --git a/client/src/state/services/productApi.ts b/client/src/state/services/productApi.ts index 9dd032a..6d48680 100644 --- a/client/src/state/services/productApi.ts +++ b/client/src/state/services/productApi.ts @@ -1,6 +1,6 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { ENV } from '@/conf' -import { ProductType } from '@/types' +import type { ProductType } from '@/types' export const productApi = createApi({ reducerPath: 'api', diff --git a/client/src/state/slices/cartSlice.ts b/client/src/state/slices/cartSlice.ts index 4e5764b..7e4d705 100644 --- a/client/src/state/slices/cartSlice.ts +++ b/client/src/state/slices/cartSlice.ts @@ -1,5 +1,5 @@ -import { CartType } from '@/types' -import { PayloadAction, createSlice, nanoid } from '@reduxjs/toolkit' +import type { CartType } from '@/types' +import { createSlice, nanoid } from '@reduxjs/toolkit' const initialState = JSON.parse(localStorage.getItem('cart') || '[]') @@ -29,7 +29,7 @@ const cartSlice = createSlice({ } localStorage.setItem('cart', JSON.stringify(state)) }, - removeFromCart(state, action: PayloadAction<string>) { + removeFromCart(state, action) { const newState = state.filter( (item: CartType) => item.cartItemId !== action.payload ) diff --git a/client/src/state/slices/filtersSlice.ts b/client/src/state/slices/filtersSlice.ts deleted file mode 100644 index 7c4d7dd..0000000 --- a/client/src/state/slices/filtersSlice.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import { ProductType } from '@/types' - -type InitialStateType = { - data: ProductType[] - sort: string - stateRating: string | null - priceRange: number[] - searchQuery: string - category: string -} - -export const initialState: InitialStateType = { - data: [], - sort: '', - stateRating: null, - priceRange: [10, 2000], - searchQuery: '', - category: '', -} - -const filtersSlice = createSlice({ - name: 'filters', - initialState, - reducers: { - sort(state, action: PayloadAction<string>) { - state.sort = action.payload - }, - filterRating(state, action: PayloadAction<string>) { - state.stateRating = action.payload - }, - priceRange(state, action) { - state.priceRange = action.payload - }, - filterSearch(state, action) { - state.searchQuery = action.payload - }, - categoryFilter(state, action) { - state.category = action.payload - }, - clearFilters() { - return initialState - }, - }, -}) - -export const { - filterRating, - sort, - priceRange, - filterSearch, - categoryFilter, - clearFilters, -} = filtersSlice.actions - -export default filtersSlice.reducer diff --git a/client/src/state/slices/index.ts b/client/src/state/slices/index.ts index eba2234..af9b954 100644 --- a/client/src/state/slices/index.ts +++ b/client/src/state/slices/index.ts @@ -1,4 +1,3 @@ import cartReducer from './cartSlice' -import filtersReducer from './filtersSlice' -export { cartReducer, filtersReducer } +export { cartReducer } diff --git a/client/src/state/store.ts b/client/src/state/store.ts index 768e9a1..ec4841a 100644 --- a/client/src/state/store.ts +++ b/client/src/state/store.ts @@ -1,19 +1,22 @@ import { configureStore } from '@reduxjs/toolkit' -import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' +import type { TypedUseSelectorHook } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' +import { setupListeners } from '@reduxjs/toolkit/query' import { productApi } from './services' -import { cartReducer, filtersReducer } from './slices' +import { cartReducer } from './slices' const store = configureStore({ reducer: { cart: cartReducer, - filter: filtersReducer, [productApi.reducerPath]: productApi.reducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(productApi.middleware), }) +setupListeners(store.dispatch) + // Types for store. type RootState = ReturnType<typeof store.getState> type AppDispatch = typeof store.dispatch diff --git a/client/src/types/index.ts b/client/src/types/index.ts index bc7e7e2..c15ca15 100644 --- a/client/src/types/index.ts +++ b/client/src/types/index.ts @@ -11,17 +11,7 @@ export type ProductType = { thumbnail: string } -export type CartType = { - id: number - title: string - description: string - price: number - discountPercentage: number - rating: number - stock: number - brand: string - category: string - thumbnail: string +export type CartType = ProductType & { quantity: number cartItemId: string } diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts index 898d270..344e0c0 100644 --- a/client/src/utils/index.ts +++ b/client/src/utils/index.ts @@ -1,4 +1,4 @@ -import { CartType } from '../types' +import type { CartType } from '@/types' export const totalCartPrice = (cartData: CartType[]) => { let cartPrice = 0 diff --git a/server/src/app.js b/server/src/app.js index 1246e86..415fcaa 100644 --- a/server/src/app.js +++ b/server/src/app.js @@ -17,7 +17,7 @@ app.use(express.static('public')) app.use(cookieParser()) // Routes -import { Payment } from './Models/payment.model.js' +import { Payment } from './models/payment.model.js' import paymentRoute from './routes/payment.routes.js' app.use('/api/v1', paymentRoute) diff --git a/server/src/controllers/payment.controller.js b/server/src/controllers/payment.controller.js index 5019ab3..bd25de6 100644 --- a/server/src/controllers/payment.controller.js +++ b/server/src/controllers/payment.controller.js @@ -1,5 +1,5 @@ import crypto from 'crypto' -import { Payment } from '../Models/payment.model.js' +import { Payment } from '../models/payment.model.js' import { ENV } from '../conf/conf.js' import { CLIENT_BASE_URL } from '../constants.js' import { razorpayInstance } from '../index.js' diff --git a/server/src/Models/payment.model.js b/server/src/models/payment.model.js similarity index 100% rename from server/src/Models/payment.model.js rename to server/src/models/payment.model.js