| @@ -4,5 +4,8 @@ | |||
| "editor.codeActionsOnSave": { | |||
| "source.fixAll": true, | |||
| "source.organizeImports": true | |||
| }, | |||
| "[javascriptreact]": { | |||
| "editor.defaultFormatter": "esbenp.prettier-vscode" | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| import { Box } from '@mui/system'; | |||
| import ProductType from '../product-type/ProductType'; | |||
| import Sort from '../sort/sort'; | |||
| const FilterSort = ({ | |||
| sort, | |||
| handleSortChange, | |||
| productType, | |||
| handleProductTypeChange, | |||
| }) => { | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: { xs: 'column', sm: 'row' }, | |||
| justifyContent: { xs: 'center' }, | |||
| alignItems: { xs: 'center' }, | |||
| }} | |||
| > | |||
| <Sort sort={sort} handleSortChange={handleSortChange} /> | |||
| <ProductType | |||
| productType={productType} | |||
| handleProductTypeChange={handleProductTypeChange} | |||
| /> | |||
| </Box> | |||
| ); | |||
| }; | |||
| export default FilterSort; | |||
| @@ -0,0 +1,35 @@ | |||
| import Box from '@mui/material/Box'; | |||
| import CircularProgress from '@mui/material/CircularProgress'; | |||
| const Loader = ({ loading }) => { | |||
| return ( | |||
| loading && ( | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| zIndex: 99, | |||
| height: '100vh', | |||
| width: '100vw', | |||
| justifyContent: 'center', | |||
| alignItems: 'center', | |||
| position: 'fixed', | |||
| top: 0, | |||
| left: 0, | |||
| }} | |||
| > | |||
| <Box | |||
| sx={{ | |||
| position: 'absolute', | |||
| top: '48%', | |||
| left: '48%', | |||
| marginX: 'auto', | |||
| }} | |||
| > | |||
| <CircularProgress color="inherit" size={60} thickness={4} /> | |||
| </Box> | |||
| </Box> | |||
| ) | |||
| ); | |||
| }; | |||
| export default Loader; | |||
| @@ -1,25 +1,41 @@ | |||
| import { Button, Typography } from '@mui/material'; | |||
| import { Box } from '@mui/system'; | |||
| import Image from 'next/image'; | |||
| import NextLink from 'next/link'; | |||
| import { useStore, useStoreUpdate } from '../../store/cart-context'; | |||
| const ProductCard = () => { | |||
| const ProductCard = ({ product }) => { | |||
| const { addCartValue } = useStoreUpdate(); | |||
| const { cartStorage } = useStore(); | |||
| const addProductToCart = (quantity) => addCartValue(product, quantity); | |||
| const inCart = cartStorage?.some( | |||
| (item) => item.product.customID === product.customID | |||
| ) | |||
| ? true | |||
| : false; | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| width: '100%', | |||
| height: '590px', | |||
| height: '100%', | |||
| border: 'none', | |||
| mb: '75px', | |||
| backgroundColor: '#F5ECD4', | |||
| }} | |||
| > | |||
| <Box width="100%"> | |||
| <Image | |||
| src="/images/product-card-image.jpg" | |||
| alt="product image" | |||
| width={373.33} | |||
| height={249} | |||
| /> | |||
| <NextLink | |||
| style={{ cursor: 'pointer' }} | |||
| href={`/products/${product.customID}`} | |||
| passHref | |||
| > | |||
| <Image | |||
| src="/images/product-card-image.jpg" | |||
| alt="product image" | |||
| width={630} | |||
| height={390} | |||
| /> | |||
| </NextLink> | |||
| </Box> | |||
| <Box | |||
| width="100%" | |||
| @@ -29,17 +45,18 @@ const ProductCard = () => { | |||
| }} | |||
| > | |||
| <Typography fontSize="24px" align="center" pt={1} pb={3}> | |||
| MINIMALIST PRINTED MUG | |||
| {product.name} | |||
| </Typography> | |||
| <Typography align="center" fontSize="18px" m={2}> | |||
| Our simple and sturdy mugs are made to last. With a minimalist desings | |||
| you will soon be enjoying your next brew. | |||
| {product.description} | |||
| </Typography> | |||
| <Typography fontSize="24px" align="center" pt={4}> | |||
| $20 | |||
| ${product.price} | |||
| </Typography> | |||
| <Box textAlign="center" mt={1}> | |||
| <Button | |||
| disabled={inCart} | |||
| onClick={() => addProductToCart(1)} | |||
| sx={{ | |||
| backgroundColor: '#CBA213', | |||
| height: 50, | |||
| @@ -47,7 +64,7 @@ const ProductCard = () => { | |||
| color: 'white', | |||
| }} | |||
| > | |||
| Add to cart | |||
| {inCart ? 'In Cart' : 'Add to cart'} | |||
| </Button> | |||
| </Box> | |||
| </Box> | |||
| @@ -12,8 +12,9 @@ const ProductType = ({ productType, handleProductTypeChange }) => { | |||
| value={productType} | |||
| onChange={handleProductTypeChange} | |||
| > | |||
| <MenuItem value="asc">Name - A-Z</MenuItem> | |||
| <MenuItem value="desc">Name - Z-A</MenuItem> | |||
| <MenuItem value="All">All</MenuItem> | |||
| <MenuItem value="Coffee">Coffee</MenuItem> | |||
| <MenuItem value="Mug">Mug</MenuItem> | |||
| </Select> | |||
| </FormControl> | |||
| </> | |||
| @@ -0,0 +1,119 @@ | |||
| import { Button, Container, Grid } from '@mui/material'; | |||
| import { Box } from '@mui/system'; | |||
| import Image from 'next/image'; | |||
| import { useMemo, useState } from 'react'; | |||
| import { useFetchProductsByCategory } from '../../hooks/useFetchProductData'; | |||
| import { compare } from '../../utils/helpers/sortHelpers'; | |||
| import ProductCard from '../product-card/ProductCard'; | |||
| const ProductsGrid = ({ | |||
| allProducts, | |||
| hasNextPage, | |||
| productType, | |||
| fetchNextPage, | |||
| sort, | |||
| }) => { | |||
| const productsPerPage = 9; | |||
| const [next, setNext] = useState(productsPerPage); | |||
| const { data: filteredData } = useFetchProductsByCategory(productType); | |||
| const allItems = useMemo( | |||
| () => allProducts?.pages?.flatMap((page) => page.product), | |||
| [allProducts] | |||
| ); | |||
| const dataToDisplay = | |||
| productType === 'All' || productType === '' | |||
| ? allItems.sort(compare('name', sort)).map((item) => ( | |||
| <Grid key={item._id} item md={4} sm={6} xs={12} sx={{ mb: '100px' }}> | |||
| <ProductCard product={item} /> | |||
| </Grid> | |||
| )) | |||
| : filteredData?.productsByCategory | |||
| .slice(0, next) | |||
| .sort(compare('name', sort)) | |||
| .map((item) => ( | |||
| <Grid | |||
| key={item._id} | |||
| item | |||
| md={4} | |||
| sm={6} | |||
| xs={12} | |||
| sx={{ mb: '100px' }} | |||
| > | |||
| <ProductCard product={item} /> | |||
| </Grid> | |||
| )); | |||
| const handleMoreProducts = () => { | |||
| setNext(next + productsPerPage); | |||
| }; | |||
| return ( | |||
| <Container | |||
| sx={{ | |||
| mt: 10, | |||
| }} | |||
| > | |||
| <Grid container spacing={2}> | |||
| {dataToDisplay} | |||
| </Grid> | |||
| <Box textAlign="center" mt={-5} mb={5}> | |||
| {hasNextPage && (productType === 'All' || productType === '') && ( | |||
| <Button | |||
| onClick={fetchNextPage} | |||
| startIcon={ | |||
| <Image | |||
| src="/images/arrow.svg" | |||
| alt="arrow down" | |||
| width={29} | |||
| height={29} | |||
| /> | |||
| } | |||
| sx={{ | |||
| backgroundColor: 'primary.main', | |||
| height: 50, | |||
| width: 150, | |||
| color: 'white', | |||
| ':hover': { | |||
| bgcolor: 'primary.main', // theme.palette.primary.main | |||
| color: 'white', | |||
| }, | |||
| }} | |||
| > | |||
| Load More | |||
| </Button> | |||
| )} | |||
| {filteredData && next < filteredData.productsByCategory.length && ( | |||
| <Button | |||
| onClick={handleMoreProducts} | |||
| startIcon={ | |||
| <Image | |||
| src="/images/arrow.svg" | |||
| alt="arrow down" | |||
| width={29} | |||
| height={29} | |||
| /> | |||
| } | |||
| sx={{ | |||
| backgroundColor: 'primary.main', | |||
| height: 50, | |||
| width: 150, | |||
| color: 'white', | |||
| ':hover': { | |||
| bgcolor: 'primary.main', // theme.palette.primary.main | |||
| color: 'white', | |||
| }, | |||
| }} | |||
| > | |||
| Load More | |||
| </Button> | |||
| )} | |||
| </Box> | |||
| </Container> | |||
| ); | |||
| }; | |||
| export default ProductsGrid; | |||
| @@ -1,106 +1,42 @@ | |||
| import { Button, Container, Grid, Typography } from '@mui/material'; | |||
| import { Container, Typography } from '@mui/material'; | |||
| import { Box } from '@mui/system'; | |||
| import Image from 'next/image'; | |||
| import ProductCard from '../product-card/ProductCard'; | |||
| import ProductType from '../product-type/ProductType'; | |||
| import Sort from '../sort/sort'; | |||
| const ProductsHero = () => { | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| width: '100%', | |||
| height: '100%', | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| }} | |||
| > | |||
| <Container | |||
| maxWidth="lg" | |||
| sx={{ | |||
| width: '1273px', | |||
| height: '350px', | |||
| mt: 25, | |||
| mb: 10, | |||
| }} | |||
| > | |||
| <Typography | |||
| fontFamily={'body1.fontFamily'} | |||
| height="120px" | |||
| fontSize="64px" | |||
| align="center" | |||
| color="primary.main" | |||
| mb={3} | |||
| sx={{ | |||
| fontSize: { md: '64px', sm: '46px', xs: '32px' }, | |||
| }} | |||
| > | |||
| Welcome to our Store! | |||
| </Typography> | |||
| <Typography fontSize="24px" align="center"> | |||
| <Typography | |||
| sx={{ fontSize: { xs: '16px', sm: '18px', md: '24px' } }} | |||
| align="center" | |||
| > | |||
| Our focus is to bring you the very best in the world of coffee. | |||
| Everything from fresh coffee beans, the best coffee powders and | |||
| capsules as well as other miscellaneous items such as cups and mugs. | |||
| Take a look to see if anything takes your fancy. | |||
| </Typography> | |||
| </Container> | |||
| <Box textAlign="center" width="100%"> | |||
| <Sort /> | |||
| <ProductType /> | |||
| </Box> | |||
| <Container | |||
| sx={{ | |||
| mt: 10, | |||
| }} | |||
| > | |||
| <Grid container spacing={2}> | |||
| <Grid item md={4} xs={12} sx={{ height: '500px' }}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| <Grid item md={4} xs={12}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| <Grid item md={4} xs={12}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| <Grid item md={4} xs={12}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| <Grid item md={4} xs={12}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| <Grid item md={4} xs={12}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| <Grid item md={4} xs={12}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| <Grid item md={4} xs={12}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| <Grid item md={4} xs={12}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| </Grid> | |||
| <Box textAlign="center" mt={-3} mb={5}> | |||
| <Button | |||
| startIcon={ | |||
| <Image | |||
| src="/images/arrow.svg" | |||
| alt="arrow down" | |||
| width={29} | |||
| height={29} | |||
| /> | |||
| } | |||
| sx={{ | |||
| backgroundColor: 'primary.main', | |||
| height: 50, | |||
| width: 150, | |||
| color: 'white', | |||
| ':hover': { | |||
| bgcolor: 'primary.main', // theme.palette.primary.main | |||
| color: 'white', | |||
| }, | |||
| }} | |||
| > | |||
| Load More | |||
| </Button> | |||
| </Box> | |||
| </Container> | |||
| </Box> | |||
| ); | |||
| }; | |||
| @@ -120,6 +120,6 @@ ProductInfo.propTypes = { | |||
| bColor: PropType.string, | |||
| side: PropType.string, | |||
| addProductToCart: PropType.func, | |||
| inCart: PropType.Boolean | PropType.undefined, | |||
| inCart: PropType.bool, | |||
| }; | |||
| export default ProductInfo; | |||
| @@ -3,7 +3,13 @@ import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'; | |||
| const Sort = ({ sort, handleSortChange }) => { | |||
| return ( | |||
| <> | |||
| <FormControl sx={{ width: '200px', paddingRight: '15px' }}> | |||
| <FormControl | |||
| sx={{ | |||
| width: '200px', | |||
| mb: { xs: '10px', sm: '0px' }, | |||
| mr: { sm: '10px' }, | |||
| }} | |||
| > | |||
| <InputLabel id="sort-label">Sort</InputLabel> | |||
| <Select | |||
| MenuProps={{ | |||
| @@ -1,4 +1,3 @@ | |||
| import { Typography } from '@mui/material'; | |||
| import { Box } from '@mui/system'; | |||
| const TabPanel = ({ children, value, index, ...other }) => { | |||
| @@ -9,10 +8,16 @@ const TabPanel = ({ children, value, index, ...other }) => { | |||
| id={`simple-tabpanel-${index}`} | |||
| aria-labelledby={`simple-tab-${index}`} | |||
| {...other} | |||
| style={{ height: '80%' }} | |||
| > | |||
| {value === index && ( | |||
| <Box sx={{ p: 3 }}> | |||
| <Typography>{children}</Typography> | |||
| <Box | |||
| display="flex" | |||
| flexDirection="column" | |||
| alignContent="space-between" | |||
| sx={{ pt: 3, pl: 3, width: '100%', height: '100%' }} | |||
| > | |||
| {children} | |||
| </Box> | |||
| )} | |||
| </div> | |||
| @@ -0,0 +1,28 @@ | |||
| import { useQuery } from '@tanstack/react-query'; | |||
| import { getProductData } from '../requests/products/producDataRequest'; | |||
| import { getProductsByCategory } from '../requests/products/productsByCategoryRequest'; | |||
| export const useFetchSingleProduct = (customID) => { | |||
| return useQuery( | |||
| ['product', customID], | |||
| async () => await getProductData(customID) | |||
| ); | |||
| }; | |||
| export const useFetchSimilarProducts = (category) => { | |||
| return useQuery( | |||
| ['products', category], | |||
| async () => await getProductsByCategory(category), | |||
| { | |||
| enabled: !!category, | |||
| } | |||
| ); | |||
| }; | |||
| export const useFetchProductsByCategory = (productType) => { | |||
| return useQuery( | |||
| ['filteredProducts', productType], | |||
| async () => await getProductsByCategory(productType), | |||
| { enabled: productType === 'Mug' || productType === 'Coffee' } | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,21 @@ | |||
| import { useInfiniteQuery } from '@tanstack/react-query'; | |||
| import { getAllProducts } from '../requests/products/productRequest'; | |||
| export const useInfiniteProducts = (filter) => { | |||
| return useInfiniteQuery( | |||
| ['products'], | |||
| async ({ pageParam = 1 }) => await getAllProducts(pageParam), | |||
| { | |||
| getNextPageParam: (lastPage, pages) => { | |||
| const maxPages = Math.ceil(lastPage?.productCount / 9); | |||
| const nextPage = pages.length + 1; | |||
| if (nextPage <= maxPages) { | |||
| return nextPage; | |||
| } | |||
| }, | |||
| enabled: filter === 'All' || filter === '', | |||
| staleTime: 0, | |||
| cacheTime: 0, | |||
| } | |||
| ); | |||
| }; | |||
| @@ -4,6 +4,7 @@ import { | |||
| QueryClient, | |||
| QueryClientProvider, | |||
| } from '@tanstack/react-query'; | |||
| import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; | |||
| import { SessionProvider } from 'next-auth/react'; | |||
| import { appWithTranslation } from 'next-i18next'; | |||
| import Head from 'next/head'; | |||
| @@ -38,6 +39,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) { | |||
| </StorageProvider> | |||
| </ThemeProvider> | |||
| </SessionProvider> | |||
| <ReactQueryDevtools initialIsOpen={false}></ReactQueryDevtools> | |||
| </Hydrate> | |||
| </QueryClientProvider> | |||
| ); | |||
| @@ -0,0 +1,199 @@ | |||
| import { Button, Grid, Tab, Tabs, Typography } from '@mui/material'; | |||
| import { Box, Container } from '@mui/system'; | |||
| import { dehydrate, QueryClient } from '@tanstack/react-query'; | |||
| import Image from 'next/image'; | |||
| import { useRouter } from 'next/router'; | |||
| import React, { useState } from 'react'; | |||
| import Loader from '../../components/loader/Loader'; | |||
| import ProductCard from '../../components/product-card/ProductCard'; | |||
| import TabPanel from '../../components/tab-panel/TabPanel'; | |||
| import { | |||
| useFetchSimilarProducts, | |||
| useFetchSingleProduct, | |||
| } from '../../hooks/useFetchProductData'; | |||
| import { getProductData } from '../../requests/products/producDataRequest'; | |||
| import { useStore, useStoreUpdate } from '../../store/cart-context'; | |||
| import { shuffle } from '../../utils/helpers/shuffle'; | |||
| const SingleProduct = () => { | |||
| const { addCartValue } = useStoreUpdate(); | |||
| const { cartStorage } = useStore(); | |||
| const router = useRouter(); | |||
| const { customId } = router.query; | |||
| const { data, isLoading } = useFetchSingleProduct(customId); | |||
| const productCategory = data?.product.category; | |||
| const { data: similarProducts, isLoading: similarLoading } = | |||
| useFetchSimilarProducts(productCategory); | |||
| const [value, setValue] = useState(0); | |||
| const addProductToCart = (quantity) => addCartValue(data.product, quantity); | |||
| const inCart = cartStorage?.some( | |||
| (item) => item.product.customID === data?.product.customID | |||
| ) | |||
| ? true | |||
| : false; | |||
| const handleChange = (event, newValue) => { | |||
| setValue(newValue); | |||
| }; | |||
| function a11yProps(index) { | |||
| return { | |||
| id: `simple-tab-${index}`, | |||
| 'aria-controls': `simple-tabpanel-${index}`, | |||
| }; | |||
| } | |||
| if (isLoading) { | |||
| return <Loader loading={isLoading} />; | |||
| } | |||
| if (similarLoading) { | |||
| return <Loader loading={similarLoading} />; | |||
| } | |||
| const productsToShow = (id) => { | |||
| const filtered = shuffle(similarProducts?.productsByCategory) | |||
| .filter((product) => product.customID !== id) | |||
| .slice(0, 3) | |||
| .map((item) => ( | |||
| <Grid | |||
| key={item._id} | |||
| item | |||
| lg={4} | |||
| md={6} | |||
| sm={6} | |||
| xs={12} | |||
| sx={{ mb: '100px' }} | |||
| > | |||
| <ProductCard product={item} /> | |||
| </Grid> | |||
| )); | |||
| return filtered; | |||
| }; | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| }} | |||
| > | |||
| <Container> | |||
| <Typography | |||
| fontFamily={'body1.fontFamily'} | |||
| fontSize="32px" | |||
| sx={{ mt: 25, height: '100%', color: 'primary.main' }} | |||
| > | |||
| {data.product.name} | |||
| </Typography> | |||
| <Grid container spacing={2}> | |||
| <Grid item md={6} sm={12}> | |||
| <Image | |||
| src="/images/product-card-image.jpg" | |||
| alt="product" | |||
| width={900} | |||
| height={600} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={12} md={6}> | |||
| <Tabs | |||
| sx={{ | |||
| '& button:focus': { | |||
| borderTop: '1px solid black', | |||
| borderLeft: '1px solid black', | |||
| borderRight: '1px solid black', | |||
| borderRadius: '5px 5px 0 0', | |||
| borderBottom: 'none', | |||
| }, | |||
| }} | |||
| value={value} | |||
| onChange={handleChange} | |||
| aria-label="basic tabs example" | |||
| > | |||
| <Tab | |||
| sx={{ | |||
| width: '50%', | |||
| }} | |||
| label="Purchase" | |||
| {...a11yProps(0)} | |||
| /> | |||
| <Tab sx={{ width: '50%' }} label="Category" {...a11yProps(1)} /> | |||
| </Tabs> | |||
| <TabPanel value={value} index={0}> | |||
| <Box flexGrow={2} sx={{ pb: { xs: '70px' } }}> | |||
| <Typography>{data.product.description}</Typography> | |||
| </Box> | |||
| <Box | |||
| sx={{ | |||
| display: { xs: 'flex' }, | |||
| flexDirection: { xs: 'column' }, | |||
| justifyContent: { xs: 'center' }, | |||
| alignItems: { xs: 'center', md: 'flex-end' }, | |||
| }} | |||
| > | |||
| <Typography mb={2}>${data.product.price}</Typography> | |||
| <Button | |||
| disabled={inCart} | |||
| onClick={() => addProductToCart(1)} | |||
| sx={{ | |||
| backgroundColor: '#CBA213', | |||
| height: 50, | |||
| width: { xs: '300px', md: '150px' }, | |||
| color: 'white', | |||
| }} | |||
| > | |||
| {inCart ? 'In Cart' : 'Add to cart'} | |||
| </Button> | |||
| </Box> | |||
| </TabPanel> | |||
| <TabPanel value={value} index={1}> | |||
| <Box sx={{ mb: { xs: '60px' } }}>{data.product.category}</Box> | |||
| </TabPanel> | |||
| </Grid> | |||
| </Grid> | |||
| <Typography | |||
| sx={{ | |||
| mt: { xs: '60px', md: '100px', lg: '150px' }, | |||
| mb: 5, | |||
| color: 'primary.main', | |||
| fontSize: '32px', | |||
| }} | |||
| > | |||
| Other Product You May Like | |||
| </Typography> | |||
| <Grid container spacing={2}> | |||
| {productsToShow(customId)} | |||
| </Grid> | |||
| </Container> | |||
| </Box> | |||
| ); | |||
| }; | |||
| export const getServerSideProps = async (context) => { | |||
| const { params } = context; | |||
| const { customId } = params; | |||
| const queryClient = new QueryClient(); | |||
| await queryClient.prefetchQuery( | |||
| ['product', customId], | |||
| async () => await getProductData(customId) | |||
| ); | |||
| return { | |||
| props: { | |||
| dehydratatedState: dehydrate(queryClient), | |||
| }, | |||
| }; | |||
| }; | |||
| export default SingleProduct; | |||
| @@ -1,119 +0,0 @@ | |||
| import { Grid, Tab, Tabs, Typography } from '@mui/material'; | |||
| import { Box, Container } from '@mui/system'; | |||
| import Image from 'next/image'; | |||
| import React, { useState } from 'react'; | |||
| import ProductCard from '../../components/product-card/ProductCard'; | |||
| import TabPanel from '../../components/tab-panel/TabPanel'; | |||
| const SingleProduct = () => { | |||
| const [value, setValue] = useState(0); | |||
| const handleChange = (event, newValue) => { | |||
| setValue(newValue); | |||
| }; | |||
| function a11yProps(index) { | |||
| return { | |||
| id: `simple-tab-${index}`, | |||
| 'aria-controls': `simple-tabpanel-${index}`, | |||
| }; | |||
| } | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| width: '100%', | |||
| height: '100%', | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| }} | |||
| > | |||
| <Container | |||
| sx={{ | |||
| width: '1273px', | |||
| }} | |||
| > | |||
| <Typography | |||
| fontFamily={'body1.fontFamily'} | |||
| fontSize="32px" | |||
| sx={{ mt: 25, height: '100%', color: 'primary.main' }} | |||
| > | |||
| Minimalist Printed Mug | |||
| </Typography> | |||
| <Grid container spacing={2} sx={{ height: '100%', width: '100%' }}> | |||
| <Grid item lg={6}> | |||
| <Image | |||
| src="/images/product-card-image.jpg" | |||
| alt="product" | |||
| width={630} | |||
| height={390} | |||
| /> | |||
| </Grid> | |||
| <Grid item lg={6}> | |||
| <Tabs | |||
| sx={{ | |||
| '& button:focus': { | |||
| borderTop: '1px solid black', | |||
| borderLeft: '1px solid black', | |||
| borderRight: '1px solid black', | |||
| borderRadius: '5px 5px 0 0', | |||
| borderBottom: 'none', | |||
| }, | |||
| }} | |||
| value={value} | |||
| onChange={handleChange} | |||
| aria-label="basic tabs example" | |||
| > | |||
| <Tab | |||
| sx={{ | |||
| width: '50%', | |||
| }} | |||
| label="Purchase" | |||
| {...a11yProps(0)} | |||
| /> | |||
| <Tab sx={{ width: '50%' }} label="Category" {...a11yProps(1)} /> | |||
| </Tabs> | |||
| <TabPanel value={value} index={0}> | |||
| <Box display="flex" flexDirection="row" justifyContent="right"> | |||
| <Box> | |||
| <Typography> | |||
| Our simple and sturdy mugs are made to last. With a | |||
| minimalist desings you will soon be enjoying your next brew. | |||
| </Typography> | |||
| </Box> | |||
| <Box | |||
| justifyContent="flex-end" | |||
| sx={{ display: 'flex', flexDirection: 'column' }} | |||
| > | |||
| <Typography align="right">$20</Typography> | |||
| </Box> | |||
| </Box> | |||
| </TabPanel> | |||
| <TabPanel value={value} index={1}> | |||
| Mugs & Cups | |||
| </TabPanel> | |||
| </Grid> | |||
| </Grid> | |||
| <Typography | |||
| sx={{ mt: 25, mb: 5, color: 'primary.main', fontSize: '32px' }} | |||
| > | |||
| Other Product You May Like | |||
| </Typography> | |||
| <Grid container spacing={2} sx={{ height: '100%', width: '100%' }}> | |||
| <Grid item lg={4}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| <Grid item lg={4}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| <Grid item lg={4}> | |||
| <ProductCard /> | |||
| </Grid> | |||
| </Grid> | |||
| </Container> | |||
| </Box> | |||
| ); | |||
| }; | |||
| export default SingleProduct; | |||
| @@ -1,32 +1,59 @@ | |||
| import { Box } from '@mui/system'; | |||
| import Head from 'next/head'; | |||
| import { useState } from 'react'; | |||
| import FilterSort from '../../components/filter-sort/FilterSort'; | |||
| import Loader from '../../components/loader/Loader'; | |||
| import ProductsGrid from '../../components/products-grid/ProductsGrid'; | |||
| import ProductsHero from '../../components/products-hero/ProductsHero'; | |||
| import { useInfiniteProducts } from '../../hooks/useInfiniteQuery'; | |||
| const Products = () => { | |||
| return ( | |||
| <> | |||
| <Box sx={{ width: '100%', height: '100%' }}> | |||
| <Head> | |||
| <title>NextJS template</title> | |||
| <meta name="description" content="Random data with pagination..." /> | |||
| </Head> | |||
| <ProductsHero /> | |||
| </Box> | |||
| </> | |||
| ); | |||
| }; | |||
| const [filter, setFilter] = useState(''); | |||
| const [sort, setSort] = useState(''); | |||
| const { data, isLoading, fetchNextPage, hasNextPage } = | |||
| useInfiniteProducts(filter); | |||
| const handleProductTypeChange = (event) => { | |||
| const filterText = event.target.value; | |||
| setFilter(filterText); | |||
| }; | |||
| // export async function getStaticProps({ locale }) { | |||
| // const queryClient = new QueryClient(); | |||
| const handleSortChange = (event) => { | |||
| const sort = event.target.value; | |||
| setSort(sort); | |||
| }; | |||
| // await queryClient.prefetchQuery(['randomData', 1], () => getData(1)); | |||
| if (isLoading) { | |||
| return <Loader loading={isLoading} />; | |||
| } | |||
| // return { | |||
| // props: { | |||
| // dehydratedState: dehydrate(queryClient), | |||
| // ...(await serverSideTranslations(locale, ['pagination'])), | |||
| // }, | |||
| // }; | |||
| // } | |||
| return ( | |||
| <Box | |||
| sx={{ | |||
| display: 'flex', | |||
| flexDirection: 'column', | |||
| }} | |||
| > | |||
| <Head> | |||
| <title>NextJS template</title> | |||
| <meta name="description" content="Random data with pagination..." /> | |||
| </Head> | |||
| <ProductsHero /> | |||
| <FilterSort | |||
| handleProductTypeChange={handleProductTypeChange} | |||
| productType={filter} | |||
| sort={sort} | |||
| handleSortChange={handleSortChange} | |||
| /> | |||
| <ProductsGrid | |||
| allProducts={data} | |||
| sort={sort} | |||
| productType={filter} | |||
| fetchNextPage={fetchNextPage} | |||
| hasNextPage={hasNextPage} | |||
| /> | |||
| </Box> | |||
| ); | |||
| }; | |||
| export default Products; | |||
| @@ -0,0 +1,10 @@ | |||
| export const shuffle = (array) => { | |||
| const newArray = [...array]; | |||
| newArray.reverse().forEach((item, index) => { | |||
| const j = Math.floor(Math.random() * (index + 1)); | |||
| [newArray[index], newArray[j]] = [newArray[j], newArray[index]]; | |||
| }); | |||
| return newArray; | |||
| }; | |||
| @@ -1,11 +1,20 @@ | |||
| export const compare = (a, b, sort) => { | |||
| if (sort === 'asc') { | |||
| if (a > b) return 1; | |||
| else if (b > a) return -1; | |||
| return 0; | |||
| } else if (sort === 'desc') { | |||
| if (a > b) return -1; | |||
| else if (b > a) return 1; | |||
| return 0; | |||
| } | |||
| /* eslint-disable no-prototype-builtins */ | |||
| export const compare = (key, order = 'asc') => { | |||
| return function innerSort(a, b) { | |||
| if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) { | |||
| // property doesn't exist on either object | |||
| return 0; | |||
| } | |||
| const varA = typeof a[key] === 'string' ? a[key].toUpperCase() : a[key]; | |||
| const varB = typeof b[key] === 'string' ? b[key].toUpperCase() : b[key]; | |||
| let comparison = 0; | |||
| if (varA > varB) { | |||
| comparison = 1; | |||
| } else if (varA < varB) { | |||
| comparison = -1; | |||
| } | |||
| return order === 'desc' ? comparison * -1 : comparison; | |||
| }; | |||
| }; | |||