| "editor.codeActionsOnSave": { | "editor.codeActionsOnSave": { | ||||
| "source.fixAll": true, | "source.fixAll": true, | ||||
| "source.organizeImports": true | "source.organizeImports": true | ||||
| }, | |||||
| "[javascriptreact]": { | |||||
| "editor.defaultFormatter": "esbenp.prettier-vscode" | |||||
| } | } | ||||
| } | } |
| 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; |
| 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; |
| import { Button, Typography } from '@mui/material'; | import { Button, Typography } from '@mui/material'; | ||||
| import { Box } from '@mui/system'; | import { Box } from '@mui/system'; | ||||
| import Image from 'next/image'; | 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 ( | return ( | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| width: '100%', | width: '100%', | ||||
| height: '590px', | |||||
| height: '100%', | |||||
| border: 'none', | border: 'none', | ||||
| mb: '75px', | mb: '75px', | ||||
| backgroundColor: '#F5ECD4', | backgroundColor: '#F5ECD4', | ||||
| }} | }} | ||||
| > | > | ||||
| <Box width="100%"> | <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> | ||||
| <Box | <Box | ||||
| width="100%" | width="100%" | ||||
| }} | }} | ||||
| > | > | ||||
| <Typography fontSize="24px" align="center" pt={1} pb={3}> | <Typography fontSize="24px" align="center" pt={1} pb={3}> | ||||
| MINIMALIST PRINTED MUG | |||||
| {product.name} | |||||
| </Typography> | </Typography> | ||||
| <Typography align="center" fontSize="18px" m={2}> | <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> | ||||
| <Typography fontSize="24px" align="center" pt={4}> | <Typography fontSize="24px" align="center" pt={4}> | ||||
| $20 | |||||
| ${product.price} | |||||
| </Typography> | </Typography> | ||||
| <Box textAlign="center" mt={1}> | <Box textAlign="center" mt={1}> | ||||
| <Button | <Button | ||||
| disabled={inCart} | |||||
| onClick={() => addProductToCart(1)} | |||||
| sx={{ | sx={{ | ||||
| backgroundColor: '#CBA213', | backgroundColor: '#CBA213', | ||||
| height: 50, | height: 50, | ||||
| color: 'white', | color: 'white', | ||||
| }} | }} | ||||
| > | > | ||||
| Add to cart | |||||
| {inCart ? 'In Cart' : 'Add to cart'} | |||||
| </Button> | </Button> | ||||
| </Box> | </Box> | ||||
| </Box> | </Box> |
| value={productType} | value={productType} | ||||
| onChange={handleProductTypeChange} | 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> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| </> | </> |
| 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; |
| import { Button, Container, Grid, Typography } from '@mui/material'; | |||||
| import { Container, Typography } from '@mui/material'; | |||||
| import { Box } from '@mui/system'; | 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 = () => { | const ProductsHero = () => { | ||||
| return ( | return ( | ||||
| <Box | <Box | ||||
| sx={{ | sx={{ | ||||
| width: '100%', | |||||
| height: '100%', | |||||
| display: 'flex', | display: 'flex', | ||||
| flexDirection: 'column', | flexDirection: 'column', | ||||
| }} | }} | ||||
| > | > | ||||
| <Container | <Container | ||||
| maxWidth="lg" | |||||
| sx={{ | sx={{ | ||||
| width: '1273px', | |||||
| height: '350px', | |||||
| mt: 25, | mt: 25, | ||||
| mb: 10, | |||||
| }} | }} | ||||
| > | > | ||||
| <Typography | <Typography | ||||
| fontFamily={'body1.fontFamily'} | fontFamily={'body1.fontFamily'} | ||||
| height="120px" | |||||
| fontSize="64px" | |||||
| align="center" | align="center" | ||||
| color="primary.main" | color="primary.main" | ||||
| mb={3} | |||||
| sx={{ | |||||
| fontSize: { md: '64px', sm: '46px', xs: '32px' }, | |||||
| }} | |||||
| > | > | ||||
| Welcome to our Store! | Welcome to our Store! | ||||
| </Typography> | </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. | Our focus is to bring you the very best in the world of coffee. | ||||
| Everything from fresh coffee beans, the best coffee powders and | Everything from fresh coffee beans, the best coffee powders and | ||||
| capsules as well as other miscellaneous items such as cups and mugs. | capsules as well as other miscellaneous items such as cups and mugs. | ||||
| Take a look to see if anything takes your fancy. | Take a look to see if anything takes your fancy. | ||||
| </Typography> | </Typography> | ||||
| </Container> | </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> | </Box> | ||||
| ); | ); | ||||
| }; | }; |
| bColor: PropType.string, | bColor: PropType.string, | ||||
| side: PropType.string, | side: PropType.string, | ||||
| addProductToCart: PropType.func, | addProductToCart: PropType.func, | ||||
| inCart: PropType.Boolean | PropType.undefined, | |||||
| inCart: PropType.bool, | |||||
| }; | }; | ||||
| export default ProductInfo; | export default ProductInfo; |
| const Sort = ({ sort, handleSortChange }) => { | const Sort = ({ sort, handleSortChange }) => { | ||||
| return ( | 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> | <InputLabel id="sort-label">Sort</InputLabel> | ||||
| <Select | <Select | ||||
| MenuProps={{ | MenuProps={{ |
| import { Typography } from '@mui/material'; | |||||
| import { Box } from '@mui/system'; | import { Box } from '@mui/system'; | ||||
| const TabPanel = ({ children, value, index, ...other }) => { | const TabPanel = ({ children, value, index, ...other }) => { | ||||
| id={`simple-tabpanel-${index}`} | id={`simple-tabpanel-${index}`} | ||||
| aria-labelledby={`simple-tab-${index}`} | aria-labelledby={`simple-tab-${index}`} | ||||
| {...other} | {...other} | ||||
| style={{ height: '80%' }} | |||||
| > | > | ||||
| {value === index && ( | {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> | </Box> | ||||
| )} | )} | ||||
| </div> | </div> |
| 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' } | |||||
| ); | |||||
| }; |
| 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, | |||||
| } | |||||
| ); | |||||
| }; |
| QueryClient, | QueryClient, | ||||
| QueryClientProvider, | QueryClientProvider, | ||||
| } from '@tanstack/react-query'; | } from '@tanstack/react-query'; | ||||
| import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; | |||||
| import { SessionProvider } from 'next-auth/react'; | import { SessionProvider } from 'next-auth/react'; | ||||
| import { appWithTranslation } from 'next-i18next'; | import { appWithTranslation } from 'next-i18next'; | ||||
| import Head from 'next/head'; | import Head from 'next/head'; | ||||
| </Providers> | </Providers> | ||||
| </ThemeProvider> | </ThemeProvider> | ||||
| </SessionProvider> | </SessionProvider> | ||||
| <ReactQueryDevtools initialIsOpen={false}></ReactQueryDevtools> | |||||
| </Hydrate> | </Hydrate> | ||||
| </QueryClientProvider> | </QueryClientProvider> | ||||
| ); | ); |
| 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; |
| 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; |
| import { Box } from '@mui/system'; | import { Box } from '@mui/system'; | ||||
| import Head from 'next/head'; | 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 ProductsHero from '../../components/products-hero/ProductsHero'; | ||||
| import { useInfiniteProducts } from '../../hooks/useInfiniteQuery'; | |||||
| const Products = () => { | 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; | export default Products; |
| 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; | |||||
| }; |
| 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; | |||||
| }; | |||||
| }; | }; |